diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 6e30b9822fe..00000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,25 +0,0 @@ -version: 2.1 -jobs: - test-arm: - machine: - image: default - resource_class: arm.medium - environment: - # Change to pin rust version - RUST_STABLE: stable - steps: - - checkout - - run: - name: Install Rust - command: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o rustup.sh - chmod +x rustup.sh - ./rustup.sh -y --default-toolchain $RUST_STABLE - source "$HOME"/.cargo/env - # Only run Tokio tests - - run: cargo test --all-features -p tokio - -workflows: - ci: - jobs: - - test-arm diff --git a/.cirrus.yml b/.cirrus.yml index 12369fffa99..6a4e7b8e5af 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -3,8 +3,8 @@ auto_cancellation: $CIRRUS_BRANCH != 'master' && $CIRRUS_BRANCH !=~ 'tokio-.*' freebsd_instance: image_family: freebsd-14-1 env: - RUST_STABLE: 1.81 - RUST_NIGHTLY: nightly-2024-05-05 + RUST_STABLE: stable + RUST_NIGHTLY: nightly-2025-01-25 RUSTFLAGS: -D warnings # Test FreeBSD in a full VM on cirrus-ci.com. Test the i686 target too, in the diff --git a/.github/buildomat/README.md b/.github/buildomat/README.md index bde5bc90760..48af2eaea8d 100644 --- a/.github/buildomat/README.md +++ b/.github/buildomat/README.md @@ -12,7 +12,7 @@ reproduce the failure on other operating systems, don't worry! The [tokio-rs/illumos] team is responsible for maintaining Tokio's illumos support, and can be called on to assist contributors with illumos-specific issues. Please feel free to tag @tokio-rs/illumos to ask for help resolving build failures on -illumos +illumos. [illumos]: https://www.illumos.org/ [Buildomat]: https://github.com/oxidecomputer/buildomat diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5b025ce023..f7e9102d7ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,9 +16,9 @@ env: RUSTUP_WINDOWS_PATH_ADD_BIN: 1 # Change to specific Rust release to pin rust_stable: stable - rust_nightly: nightly-2024-05-05 + rust_nightly: nightly-2025-01-25 # Pin a specific miri version - rust_miri_nightly: nightly-2024-09-19 + rust_miri_nightly: nightly-2025-01-25 rust_clippy: '1.77' # When updating this, also update: # - README.md @@ -47,10 +47,11 @@ jobs: - test-workspace-all-features - test-integration-tests-per-feature - test-parking_lot - - test-tracing-instrumentation - valgrind - test-unstable - - miri + - miri-lib + - miri-test + - miri-doc - asan - cross-check - cross-check-tier3 @@ -180,7 +181,7 @@ jobs: run: | set -euxo pipefail RUSTFLAGS="$RUSTFLAGS -C panic=abort -Zpanic-abort-tests" cargo nextest run --workspace --exclude tokio-macros --exclude tests-build --all-features --tests - + test-integration-tests-per-feature: needs: basics name: Run integration tests for each feature @@ -246,34 +247,6 @@ jobs: - name: Check tests with all features enabled run: cargo check --workspace --all-features --tests - test-tracing-instrumentation: - # These tests use the as-yet unpublished `tracing-mock` crate to test the - # tracing instrumentation present in Tokio. As such they are placed in - # their own test crate outside of the workspace. - needs: basics - name: test tokio instrumentation - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install Rust ${{ env.rust_stable }} - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ env.rust_stable }} - - name: Install cargo-nextest - uses: taiki-e/install-action@v2 - with: - tool: cargo-nextest - - - uses: Swatinem/rust-cache@v2 - - - name: test tracing-instrumentation - run: | - set -euxo pipefail - cargo nextest run - working-directory: tokio/tests/tracing-instrumentation - env: - RUSTFLAGS: --cfg tokio_unstable -Dwarnings - valgrind: name: valgrind needs: basics @@ -283,7 +256,7 @@ jobs: - name: Install Rust ${{ env.rust_stable }} uses: dtolnay/rust-toolchain@stable with: - toolchain: ${{ env.rust_stable }} + toolchain: 1.82 - name: Install Valgrind uses: taiki-e/install-action@valgrind @@ -409,8 +382,31 @@ jobs: # the unstable cfg to RustDoc RUSTDOCFLAGS: --cfg tokio_unstable --cfg tokio_internal_mt_counters - miri: - name: miri + miri-lib: + name: miri-lib + needs: basics + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust ${{ env.rust_miri_nightly }} + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.rust_miri_nightly }} + components: miri + - name: Install cargo-nextest + uses: taiki-e/install-action@v2 + with: + tool: cargo-nextest + - uses: Swatinem/rust-cache@v2 + - name: miri + run: | + cargo miri nextest run --features full --lib --no-fail-fast + working-directory: tokio + env: + MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields + + miri-test: + name: miri-test needs: basics runs-on: ubuntu-latest steps: @@ -427,7 +423,26 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: miri run: | - cargo miri nextest run --features full --lib --tests --no-fail-fast + cargo miri nextest run --features full --test '*' --no-fail-fast + working-directory: tokio + env: + MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields + + miri-doc: + name: miri-doc + needs: basics + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust ${{ env.rust_miri_nightly }} + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.rust_miri_nightly }} + components: miri + - uses: Swatinem/rust-cache@v2 + - name: miri-doc-test + run: | + cargo miri test --doc --all-features --no-fail-fast working-directory: tokio env: MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields @@ -496,6 +511,7 @@ jobs: strategy: matrix: target: + - name: x86_64-unknown-haiku - name: armv7-sony-vita-newlibeabihf exclude_features: "process,signal,rt-process-signal,full" steps: @@ -516,15 +532,19 @@ jobs: cross-test-with-parking_lot: needs: basics - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: matrix: include: - target: i686-unknown-linux-gnu + os: ubuntu-latest rustflags: --cfg tokio_taskdump - target: armv5te-unknown-linux-gnueabi + os: ubuntu-latest - target: armv7-unknown-linux-gnueabihf + os: ubuntu-22.04-arm # TODO: update to 24.04 when https://github.com/rust-lang/rust/issues/135867 solved - target: aarch64-unknown-linux-gnu + os: ubuntu-22.04-arm # TODO: update to 24.04 when https://github.com/rust-lang/rust/issues/135867 solved rustflags: --cfg tokio_taskdump steps: - uses: actions/checkout@v4 @@ -556,15 +576,19 @@ jobs: cross-test-without-parking_lot: needs: basics - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: matrix: include: - target: i686-unknown-linux-gnu + os: ubuntu-latest rustflags: --cfg tokio_taskdump - target: armv5te-unknown-linux-gnueabi + os: ubuntu-latest - target: armv7-unknown-linux-gnueabihf + os: ubuntu-22.04-arm # TODO: update to 24.04 when https://github.com/rust-lang/rust/issues/135867 solved - target: aarch64-unknown-linux-gnu + os: ubuntu-22.04-arm # TODO: update to 24.04 when https://github.com/rust-lang/rust/issues/135867 solved rustflags: --cfg tokio_taskdump steps: - uses: actions/checkout@v4 @@ -768,7 +792,15 @@ jobs: docs: name: docs - runs-on: ubuntu-latest + runs-on: ${{ matrix.run.os }} + strategy: + matrix: + run: + - os: windows-latest + - os: ubuntu-latest + RUSTFLAGS: --cfg tokio_taskdump + RUSTDOCFLAGS: --cfg tokio_taskdump + steps: - uses: actions/checkout@v4 - name: Install Rust ${{ env.rust_nightly }} @@ -780,8 +812,8 @@ jobs: run: | cargo doc --lib --no-deps --all-features --document-private-items env: - RUSTFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump - RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable --cfg tokio_taskdump -Dwarnings + RUSTFLAGS: --cfg docsrs --cfg tokio_unstable ${{ matrix.run.RUSTFLAGS }} + RUSTDOCFLAGS: --cfg docsrs --cfg tokio_unstable -Dwarnings ${{ matrix.run.RUSTDOCFLAGS }} loom-compile: name: build loom tests @@ -985,7 +1017,7 @@ jobs: - name: Install cargo-hack, wasmtime, and cargo-wasi uses: taiki-e/install-action@v2 with: - tool: cargo-hack,wasmtime,cargo-wasi + tool: cargo-hack,wasmtime - uses: Swatinem/rust-cache@v2 - name: WASI test tokio full @@ -1011,9 +1043,12 @@ jobs: - name: test tests-integration --features wasi-rt # TODO: this should become: `cargo hack wasi test --each-feature` - run: cargo wasi test --test rt_yield --features wasi-rt + run: cargo test --target ${{ matrix.target }} --test rt_yield --features wasi-rt if: matrix.target == 'wasm32-wasip1' working-directory: tests-integration + env: + CARGO_TARGET_WASM32_WASIP1_RUNNER: "wasmtime run --" + RUSTFLAGS: -Dwarnings -C target-feature=+atomics,+bulk-memory -C link-args=--max-memory=67108864 - name: test tests-integration --features wasi-threads-rt run: cargo test --target ${{ matrix.target }} --features wasi-threads-rt @@ -1051,23 +1086,6 @@ jobs: run: cargo check-external-types --all-features working-directory: tokio - check-unexpected-lints-cfgs: - name: check unexpected lints and cfgs - needs: basics - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install Rust ${{ env.rust_nightly }} - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.rust_nightly }} - - name: don't allow warnings - run: sed -i '/#!\[allow(unknown_lints, unexpected_cfgs)\]/d' */src/lib.rs */tests/*.rs - - name: check for unknown lints and cfgs - run: cargo check --all-features --tests - env: - RUSTFLAGS: -Dwarnings --check-cfg=cfg(loom,tokio_unstable,tokio_taskdump,fuzzing,mio_unsupported_force_poll_poll,tokio_internal_mt_counters,fs,tokio_no_parking_lot,tokio_no_tuning_tests) -Funexpected_cfgs -Funknown_lints - check-fuzzing: name: check-fuzzing needs: basics @@ -1105,23 +1123,57 @@ jobs: - uses: actions/checkout@v4 - name: Make sure dictionary words are sorted and unique run: | - # `sed` removes the first line (number of words) and - # the last line (new line). - # + FILE="spellcheck.dic" + + # Verify the first line is an integer. + first_line=$(head -n 1 "$FILE") + if ! [[ "$first_line" =~ ^[0-9]+$ ]]; then + echo "Error: The first line of $FILE must be an integer, but got: '$first_line'" + exit 1 + fi + expected_count="$first_line" + + # Check that the number of lines matches the integer. + # xargs (with no arguments) will strip leading/trailing whitespacefrom wc's output. + actual_count=$(sed '1d' "$FILE" | wc -l | xargs) + if [ "$expected_count" -ne "$actual_count" ]; then + echo "Error: The number of lines ($actual_count) does not match $expected_count." + exit 1 + fi + + # `sed` removes the first line (number of words). + # # `sort` makes sure everything in between is sorted # and contains no duplicates. - # + # # Since `sort` is sensitive to locale, we set it # using LC_ALL to en_US.UTF8 to be consistent in different # environments. ( - sed '1d; $d' spellcheck.dic | LC_ALL=en_US.UTF8 sort -uc + sed '1d' $FILE | LC_ALL=en_US.UTF8 sort -uc ) || { echo "Dictionary is not in sorted order. Correct order is:" - LC_ALL=en_US.UTF8 sort -u <(sed '1d; $d' spellcheck.dic) + LC_ALL=en_US.UTF8 sort -u <(sed '1d' $FILE) false } - name: Run cargo-spellcheck - run: cargo spellcheck --code 1 - + run: | + if ! cargo spellcheck --code 1 + then + echo '' + echo '' + echo 'If this is a Rust method/type/variable name, then you should' + echo 'enclose it in backticks like this: `MyRustType`.' + echo '' + echo 'If this is a real word, then you can add it to spellcheck.dic' + exit 1 + fi + - name: Detect trailing whitespace + run: | + if grep --exclude-dir=.git --exclude-dir=target -rne '\s$' . + then + echo '' + echo 'Please remove trailing whitespace from these lines.' + exit 1 + fi diff --git a/.github/workflows/stress-test.yml b/.github/workflows/stress-test.yml index e5c13f6f432..1b1481f1dc8 100644 --- a/.github/workflows/stress-test.yml +++ b/.github/workflows/stress-test.yml @@ -13,7 +13,7 @@ env: RUSTFLAGS: -Dwarnings RUST_BACKTRACE: 1 # Change to specific Rust release to pin - rust_stable: stable + rust_stable: 1.82 permissions: contents: read diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 681b1c288c3..69efa24a95f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -196,12 +196,12 @@ LOOM_MAX_PREEMPTIONS=1 LOOM_MAX_BRANCHES=10000 RUSTFLAGS="--cfg loom -C debug_as cargo test --lib --release --features full -- --test-threads=1 --nocapture ``` Additionally, you can also add `--cfg tokio_unstable` to the `RUSTFLAGS` environment variable to -run loom tests that test unstable features. +run loom tests that test unstable features. You can run miri tests with ``` MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields" \ - cargo +nightly miri test --features full --lib + cargo +nightly miri test --features full --lib --tests ``` ### Performing spellcheck on tokio codebase @@ -216,8 +216,8 @@ cargo install --locked cargo-spellcheck cargo spellcheck check ``` -if the command rejects a word, you should backtick the rejected word if it's code related. If not, the -rejected word should be put into `spellcheck.dic` file. +if the command rejects a word, you should backtick the rejected word if it's code related. If not, the +rejected word should be put into `spellcheck.dic` file. Note that when you add a word into the file, you should also update the first line which tells the spellcheck tool the total number of words included in the file diff --git a/Cargo.toml b/Cargo.toml index 2238deac71c..c215946f421 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,15 @@ members = [ [workspace.metadata.spellcheck] config = "spellcheck.toml" + +[workspace.lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(fuzzing)', + 'cfg(loom)', + 'cfg(mio_unsupported_force_poll_poll)', + 'cfg(tokio_internal_mt_counters)', + 'cfg(tokio_no_parking_lot)', + 'cfg(tokio_no_tuning_tests)', + 'cfg(tokio_taskdump)', + 'cfg(tokio_unstable)', +] } diff --git a/README.md b/README.md index c59566173c3..16b0819fea3 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml: ```toml [dependencies] -tokio = { version = "1.41.1", features = ["full"] } +tokio = { version = "1.43.0", features = ["full"] } ``` Then, on your main.rs: @@ -78,7 +78,7 @@ async fn main() -> Result<(), Box> { loop { let n = match socket.read(&mut buf).await { // socket closed - Ok(n) if n == 0 => return, + Ok(0) => return, Ok(n) => n, Err(e) => { eprintln!("failed to read from socket; err = {:?}", e); @@ -216,7 +216,6 @@ warrants a patch release with a fix for the bug, it will be backported and released as a new patch release for each LTS minor version. Our current LTS releases are: - * `1.32.x` - LTS release until September 2024. (MSRV 1.63) * `1.36.x` - LTS release until March 2025. (MSRV 1.63) * `1.38.x` - LTS release until July 2025. (MSRV 1.63) @@ -238,6 +237,7 @@ tokio = { version = "~1.32", features = [...] } * `1.18.x` - LTS release until June 2023. * `1.20.x` - LTS release until September 2023. * `1.25.x` - LTS release until March 2024. + * `1.32.x` - LTS release until September 2024. ## License diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 44156fcbfb5..de39565b398 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -95,3 +95,6 @@ harness = false name = "time_timeout" path = "time_timeout.rs" harness = false + +[lints] +workspace = true diff --git a/benches/sync_mpsc.rs b/benches/sync_mpsc.rs index 1a3f37cab81..5a3f75001bf 100644 --- a/benches/sync_mpsc.rs +++ b/benches/sync_mpsc.rs @@ -37,7 +37,7 @@ fn create_medium(g: &mut BenchmarkGroup) { fn send_data(g: &mut BenchmarkGroup, prefix: &str) { let rt = rt(); - g.bench_function(format!("{}_{}", prefix, SIZE), |b| { + g.bench_function(format!("{prefix}_{SIZE}"), |b| { b.iter(|| { let (tx, mut rx) = mpsc::channel::(SIZE); diff --git a/deny.toml b/deny.toml index 6f343f80b72..09d2da871c8 100644 --- a/deny.toml +++ b/deny.toml @@ -9,7 +9,7 @@ allow = [ "Apache-2.0", ] exceptions = [ - { allow = ["Unicode-DFS-2016"], crate = "unicode-ident" }, + { allow = ["Unicode-3.0", "Unicode-DFS-2016"], crate = "unicode-ident" }, ] [bans] diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 54f2ecb8a4f..84112c08dab 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -95,3 +95,6 @@ path = "named-pipe-multi-client.rs" [[example]] name = "dump" path = "dump.rs" + +[lints] +workspace = true diff --git a/examples/chat.rs b/examples/chat.rs index c4959d38ead..1d8c6b04684 100644 --- a/examples/chat.rs +++ b/examples/chat.rs @@ -193,7 +193,7 @@ async fn process( // A client has connected, let's let everyone know. { let mut state = state.lock().await; - let msg = format!("{} has joined the chat", username); + let msg = format!("{username} has joined the chat"); tracing::info!("{}", msg); state.broadcast(addr, &msg).await; } @@ -210,7 +210,7 @@ async fn process( // broadcast this message to the other users. Some(Ok(msg)) => { let mut state = state.lock().await; - let msg = format!("{}: {}", username, msg); + let msg = format!("{username}: {msg}"); state.broadcast(addr, &msg).await; } @@ -234,7 +234,7 @@ async fn process( let mut state = state.lock().await; state.peers.remove(&addr); - let msg = format!("{} has left the chat", username); + let msg = format!("{username} has left the chat"); tracing::info!("{}", msg); state.broadcast(addr, &msg).await; } diff --git a/examples/connect.rs b/examples/connect.rs index 836d7f8f2e3..c869de8ff0c 100644 --- a/examples/connect.rs +++ b/examples/connect.rs @@ -77,7 +77,7 @@ mod tcp { //BytesMut into Bytes Ok(i) => future::ready(Some(i.freeze())), Err(e) => { - println!("failed to read from socket; error={}", e); + println!("failed to read from socket; error={e}"); future::ready(None) } }) diff --git a/examples/dump.rs b/examples/dump.rs index 6f744713f7c..c7ece458ff8 100644 --- a/examples/dump.rs +++ b/examples/dump.rs @@ -1,5 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] - //! This example demonstrates tokio's experimental task dumping functionality. //! This application deadlocks. Input CTRL+C to display traces of each task, or //! input CTRL+C twice within 1 second to quit. diff --git a/examples/echo-udp.rs b/examples/echo-udp.rs index 3027c869696..8f9ed7088eb 100644 --- a/examples/echo-udp.rs +++ b/examples/echo-udp.rs @@ -38,7 +38,7 @@ impl Server { if let Some((size, peer)) = to_send { let amt = socket.send_to(&buf[..size], &peer).await?; - println!("Echoed {}/{} bytes to {}", amt, size, peer); + println!("Echoed {amt}/{size} bytes to {peer}"); } // If we're here then `to_send` is `None`, so we take a look for the diff --git a/examples/echo.rs b/examples/echo.rs index aece8dc6593..045950ade25 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -40,7 +40,7 @@ async fn main() -> Result<(), Box> { // connections. This TCP listener is bound to the address we determined // above and must be associated with an event loop. let listener = TcpListener::bind(&addr).await?; - println!("Listening on: {}", addr); + println!("Listening on: {addr}"); loop { // Asynchronously wait for an inbound socket. diff --git a/examples/print_each_packet.rs b/examples/print_each_packet.rs index 087f9cf03eb..3e568bd62ea 100644 --- a/examples/print_each_packet.rs +++ b/examples/print_each_packet.rs @@ -75,7 +75,7 @@ async fn main() -> Result<(), Box> { // to our event loop. After the socket's created we inform that we're ready // to go and start accepting connections. let listener = TcpListener::bind(&addr).await?; - println!("Listening on: {}", addr); + println!("Listening on: {addr}"); loop { // Asynchronously wait for an inbound socket. @@ -96,8 +96,8 @@ async fn main() -> Result<(), Box> { // The stream will return None once the client disconnects. while let Some(message) = framed.next().await { match message { - Ok(bytes) => println!("bytes: {:?}", bytes), - Err(err) => println!("Socket closed with error: {:?}", err), + Ok(bytes) => println!("bytes: {bytes:?}"), + Err(err) => println!("Socket closed with error: {err:?}"), } } println!("Socket received FIN packet and closed connection"); diff --git a/examples/proxy.rs b/examples/proxy.rs index 9260a12fb56..cc912ec9533 100644 --- a/examples/proxy.rs +++ b/examples/proxy.rs @@ -38,8 +38,8 @@ async fn main() -> Result<(), Box> { .nth(2) .unwrap_or_else(|| "127.0.0.1:8080".to_string()); - println!("Listening on: {}", listen_addr); - println!("Proxying to: {}", server_addr); + println!("Listening on: {listen_addr}"); + println!("Proxying to: {server_addr}"); let listener = TcpListener::bind(listen_addr).await?; @@ -50,7 +50,7 @@ async fn main() -> Result<(), Box> { copy_bidirectional(&mut inbound, &mut outbound) .map(|r| { if let Err(e) = r { - println!("Failed to transfer; error={}", e); + println!("Failed to transfer; error={e}"); } }) .await diff --git a/examples/tinydb.rs b/examples/tinydb.rs index 5a1983df6b4..fa5e852643b 100644 --- a/examples/tinydb.rs +++ b/examples/tinydb.rs @@ -90,7 +90,7 @@ async fn main() -> Result<(), Box> { .unwrap_or_else(|| "127.0.0.1:8080".to_string()); let listener = TcpListener::bind(&addr).await?; - println!("Listening on: {}", addr); + println!("Listening on: {addr}"); // Create the shared state of this server that will be shared amongst all // clients. We populate the initial database and then create the `Database` @@ -131,11 +131,11 @@ async fn main() -> Result<(), Box> { let response = response.serialize(); if let Err(e) = lines.send(response.as_str()).await { - println!("error on sending response; error = {:?}", e); + println!("error on sending response; error = {e:?}"); } } Err(e) => { - println!("error on decoding from socket; error = {:?}", e); + println!("error on decoding from socket; error = {e:?}"); } } } @@ -143,7 +143,7 @@ async fn main() -> Result<(), Box> { // The connection will be closed at this point as `lines.next()` has returned `None`. }); } - Err(e) => println!("error accepting socket; error = {:?}", e), + Err(e) => println!("error accepting socket; error = {e:?}"), } } } @@ -162,7 +162,7 @@ fn handle_request(line: &str, db: &Arc) -> Response { value: value.clone(), }, None => Response::Error { - msg: format!("no key {}", key), + msg: format!("no key {key}"), }, }, Request::Set { key, value } => { @@ -203,7 +203,7 @@ impl Request { value: value.to_string(), }) } - Some(cmd) => Err(format!("unknown command: {}", cmd)), + Some(cmd) => Err(format!("unknown command: {cmd}")), None => Err("empty input".into()), } } @@ -212,13 +212,13 @@ impl Request { impl Response { fn serialize(&self) -> String { match *self { - Response::Value { ref key, ref value } => format!("{} = {}", key, value), + Response::Value { ref key, ref value } => format!("{key} = {value}"), Response::Set { ref key, ref value, ref previous, - } => format!("set {} = `{}`, previous: {:?}", key, value, previous), - Response::Error { ref msg } => format!("error: {}", msg), + } => format!("set {key} = `{value}`, previous: {previous:?}"), + Response::Error { ref msg } => format!("error: {msg}"), } } } diff --git a/examples/tinyhttp.rs b/examples/tinyhttp.rs index dceccf47a89..245caf07999 100644 --- a/examples/tinyhttp.rs +++ b/examples/tinyhttp.rs @@ -31,13 +31,13 @@ async fn main() -> Result<(), Box> { .nth(1) .unwrap_or_else(|| "127.0.0.1:8080".to_string()); let server = TcpListener::bind(&addr).await?; - println!("Listening on: {}", addr); + println!("Listening on: {addr}"); loop { let (stream, _) = server.accept().await?; tokio::spawn(async move { if let Err(e) = process(stream).await { - println!("failed to process connection; error = {}", e); + println!("failed to process connection; error = {e}"); } }); } @@ -159,7 +159,7 @@ impl Decoder for Http { let mut parsed_headers = [httparse::EMPTY_HEADER; 16]; let mut r = httparse::Request::new(&mut parsed_headers); let status = r.parse(src).map_err(|e| { - let msg = format!("failed to parse http request: {:?}", e); + let msg = format!("failed to parse http request: {e:?}"); io::Error::new(io::ErrorKind::Other, msg) })?; diff --git a/examples/udp-codec.rs b/examples/udp-codec.rs index c587be9abfd..7f0a9080ca2 100644 --- a/examples/udp-codec.rs +++ b/examples/udp-codec.rs @@ -46,7 +46,7 @@ async fn main() -> Result<(), Box> { // Run both futures simultaneously of `a` and `b` sending messages back and forth. match tokio::try_join!(a, b) { - Err(e) => println!("an error occurred; error = {:?}", e), + Err(e) => println!("an error occurred; error = {e:?}"), _ => println!("done!"), } diff --git a/spellcheck.dic b/spellcheck.dic index f368d2d7214..e9f7eec22f6 100644 --- a/spellcheck.dic +++ b/spellcheck.dic @@ -1,4 +1,4 @@ -292 +299 & + < @@ -30,11 +30,13 @@ 450ms 50ms 8MB +ABI +accessors adaptor adaptors Adaptors AIO -ambiant +ambient amongst api APIs @@ -75,8 +77,11 @@ datagrams deallocate deallocated Deallocates +debuginfo decrementing +demangled dequeued +dereferenced deregister deregistered deregistering @@ -133,6 +138,7 @@ implementers implementor implementors incrementing +inlining interoperate invariants Invariants @@ -274,6 +280,7 @@ unparks Unparks unreceived unsafety +unsets Unsets unsynchronized untrusted @@ -291,4 +298,3 @@ Wakers wakeup wakeups workstealing - diff --git a/stress-test/Cargo.toml b/stress-test/Cargo.toml index 60c07e4eabd..79db8ce1da1 100644 --- a/stress-test/Cargo.toml +++ b/stress-test/Cargo.toml @@ -13,3 +13,6 @@ tokio = { version = "1.0.0", path = "../tokio/", features = ["full"] } [dev-dependencies] rand = "0.8" + +[lints] +workspace = true diff --git a/tests-build/Cargo.toml b/tests-build/Cargo.toml index 639dc3d1292..ee27d6b5ab5 100644 --- a/tests-build/Cargo.toml +++ b/tests-build/Cargo.toml @@ -15,3 +15,6 @@ tokio = { version = "1.0.0", path = "../tokio", optional = true } [dev-dependencies] trybuild = "1.0" + +[lints] +workspace = true diff --git a/tests-build/README.md b/tests-build/README.md index b6b54d23e98..68d92ace051 100644 --- a/tests-build/README.md +++ b/tests-build/README.md @@ -6,5 +6,5 @@ To run all of the tests in this directory, run the following commands: cargo test --features full cargo test --features rt ``` -If one of the tests fail, you can pass `TRYBUILD=overwrite` to the `cargo test` +If any of the tests fail, you can pass `TRYBUILD=overwrite` to the `cargo test` command that failed to have it regenerate the test output. diff --git a/tests-build/tests/fail/macros_type_mismatch.rs b/tests-build/tests/fail/macros_type_mismatch.rs index 15d70770983..16dbd2d6ac7 100644 --- a/tests-build/tests/fail/macros_type_mismatch.rs +++ b/tests-build/tests/fail/macros_type_mismatch.rs @@ -15,6 +15,40 @@ async fn extra_semicolon() -> Result<(), ()> { Ok(()); } +/// This test is a characterization test for the `?` operator. +/// +/// See for more details. +/// +/// It should fail with a single error message about the return type of the function, but instead +/// if fails with an extra error message due to the `?` operator being used within the async block +/// rather than the original function. +/// +/// ```text +/// 28 | None?; +/// | ^ cannot use the `?` operator in an async block that returns `()` +/// ``` +#[tokio::main] +async fn question_mark_operator_with_invalid_option() -> Option<()> { + None?; +} + +/// This test is a characterization test for the `?` operator. +/// +/// See for more details. +/// +/// It should fail with a single error message about the return type of the function, but instead +/// if fails with an extra error message due to the `?` operator being used within the async block +/// rather than the original function. +/// +/// ```text +/// 33 | Ok(())?; +/// | ^ cannot use the `?` operator in an async block that returns `()` +/// ``` +#[tokio::main] +async fn question_mark_operator_with_invalid_result() -> Result<(), ()> { + Ok(())?; +} + // https://github.com/tokio-rs/tokio/issues/4635 #[allow(redundant_semicolons)] #[rustfmt::skip] diff --git a/tests-build/tests/fail/macros_type_mismatch.stderr b/tests-build/tests/fail/macros_type_mismatch.stderr index 2d5af0dd65c..79e543fe3bb 100644 --- a/tests-build/tests/fail/macros_type_mismatch.stderr +++ b/tests-build/tests/fail/macros_type_mismatch.stderr @@ -29,6 +29,63 @@ error[E0308]: mismatched types = note: expected enum `Result<(), ()>` found unit type `()` +error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`) + --> tests/fail/macros_type_mismatch.rs:40:9 + | +38 | #[tokio::main] + | -------------- this function should return `Result` or `Option` to accept `?` +39 | async fn question_mark_operator_with_invalid_option() -> Option<()> { +40 | None?; + | ^ cannot use the `?` operator in an async block that returns `()` + | + = help: the trait `FromResidual>` is not implemented for `()` + +error[E0308]: mismatched types + --> tests/fail/macros_type_mismatch.rs:40:5 + | +39 | async fn question_mark_operator_with_invalid_option() -> Option<()> { + | ---------- expected `Option<()>` because of return type +40 | None?; + | ^^^^^^ expected `Option<()>`, found `()` + | + = note: expected enum `Option<()>` + found unit type `()` +help: try adding an expression at the end of the block + | +40 ~ None?;; +41 + None + | +40 ~ None?;; +41 + Some(()) + | + +error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`) + --> tests/fail/macros_type_mismatch.rs:57:11 + | +55 | #[tokio::main] + | -------------- this function should return `Result` or `Option` to accept `?` +56 | async fn question_mark_operator_with_invalid_result() -> Result<(), ()> { +57 | Ok(())?; + | ^ cannot use the `?` operator in an async block that returns `()` + | + = help: the trait `FromResidual>` is not implemented for `()` + +error[E0308]: mismatched types + --> tests/fail/macros_type_mismatch.rs:57:5 + | +56 | async fn question_mark_operator_with_invalid_result() -> Result<(), ()> { + | -------------- expected `Result<(), ()>` because of return type +57 | Ok(())?; + | ^^^^^^^^ expected `Result<(), ()>`, found `()` + | + = note: expected enum `Result<(), ()>` + found unit type `()` +help: try adding an expression at the end of the block + | +57 ~ Ok(())?;; +58 + Ok(()) + | + error[E0308]: mismatched types --> tests/fail/macros_type_mismatch.rs:23:12 | diff --git a/tests-integration/Cargo.toml b/tests-integration/Cargo.toml index 74724917f15..5b15017b943 100644 --- a/tests-integration/Cargo.toml +++ b/tests-integration/Cargo.toml @@ -61,3 +61,6 @@ tokio-test = { version = "0.4", path = "../tokio-test", optional = true } doc-comment = "0.3.1" futures = { version = "0.3.0", features = ["async-await"] } bytes = "1.0.0" + +[lints] +workspace = true diff --git a/tokio-macros/CHANGELOG.md b/tokio-macros/CHANGELOG.md index 9b30d342e11..5dac1ff68ed 100644 --- a/tokio-macros/CHANGELOG.md +++ b/tokio-macros/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.5.0 (Jan 8th, 2025) + +- macros: suppress `clippy::needless_return` in `#[tokio::main]` ([#6874]) + +[#6874]: https://github.com/tokio-rs/tokio/pull/6874 + # 2.4.0 (July 22nd, 2024) - msrv: increase MSRV to 1.70 ([#6645]) diff --git a/tokio-macros/Cargo.toml b/tokio-macros/Cargo.toml index 26ff7001b73..3305385d94e 100644 --- a/tokio-macros/Cargo.toml +++ b/tokio-macros/Cargo.toml @@ -4,7 +4,7 @@ name = "tokio-macros" # - Remove path dependencies # - Update CHANGELOG.md. # - Create "tokio-macros-1.x.y" git tag. -version = "2.4.0" +version = "2.5.0" edition = "2021" rust-version = "1.70" authors = ["Tokio Contributors "] @@ -31,3 +31,6 @@ tokio = { version = "1.0.0", path = "../tokio", features = ["full"] } [package.metadata.docs.rs] all-features = true + +[lints] +workspace = true diff --git a/tokio-macros/src/entry.rs b/tokio-macros/src/entry.rs index 5b4cd32f2de..e670bf6a495 100644 --- a/tokio-macros/src/entry.rs +++ b/tokio-macros/src/entry.rs @@ -21,7 +21,7 @@ impl RuntimeFlavor { "single_thread" => Err("The single threaded runtime flavor is called `current_thread`.".to_string()), "basic_scheduler" => Err("The `basic_scheduler` runtime flavor has been renamed to `current_thread`.".to_string()), "threaded_scheduler" => Err("The `threaded_scheduler` runtime flavor has been renamed to `multi_thread`.".to_string()), - _ => Err(format!("No such runtime flavor `{}`. The runtime flavors are `current_thread` and `multi_thread`.", s)), + _ => Err(format!("No such runtime flavor `{s}`. The runtime flavors are `current_thread` and `multi_thread`.")), } } } @@ -37,7 +37,7 @@ impl UnhandledPanic { match s { "ignore" => Ok(UnhandledPanic::Ignore), "shutdown_runtime" => Ok(UnhandledPanic::ShutdownRuntime), - _ => Err(format!("No such unhandled panic behavior `{}`. The unhandled panic behaviors are `ignore` and `shutdown_runtime`.", s)), + _ => Err(format!("No such unhandled panic behavior `{s}`. The unhandled panic behaviors are `ignore` and `shutdown_runtime`.")), } } @@ -240,12 +240,12 @@ fn parse_int(int: syn::Lit, span: Span, field: &str) -> Result Ok(value), Err(e) => Err(syn::Error::new( span, - format!("Failed to parse value of `{}` as integer: {}", field, e), + format!("Failed to parse value of `{field}` as integer: {e}"), )), }, _ => Err(syn::Error::new( span, - format!("Failed to parse value of `{}` as integer.", field), + format!("Failed to parse value of `{field}` as integer."), )), } } @@ -256,7 +256,7 @@ fn parse_string(int: syn::Lit, span: Span, field: &str) -> Result Ok(s.to_string()), _ => Err(syn::Error::new( span, - format!("Failed to parse value of `{}` as string.", field), + format!("Failed to parse value of `{field}` as string."), )), } } @@ -276,7 +276,7 @@ fn parse_path(lit: syn::Lit, span: Span, field: &str) -> Result Err(syn::Error::new( span, - format!("Failed to parse value of `{}` as path.", field), + format!("Failed to parse value of `{field}` as path."), )), } } @@ -286,7 +286,7 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result Ok(b.value), _ => Err(syn::Error::new( span, - format!("Failed to parse value of `{}` as bool.", field), + format!("Failed to parse value of `{field}` as bool."), )), } } @@ -343,8 +343,7 @@ fn build_config( } name => { let msg = format!( - "Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`", - name, + "Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`", ); return Err(syn::Error::new_spanned(namevalue, msg)); } @@ -359,21 +358,19 @@ fn build_config( let msg = match name.as_str() { "threaded_scheduler" | "multi_thread" => { format!( - "Set the runtime flavor with #[{}(flavor = \"multi_thread\")].", - macro_name + "Set the runtime flavor with #[{macro_name}(flavor = \"multi_thread\")]." ) } "basic_scheduler" | "current_thread" | "single_threaded" => { format!( - "Set the runtime flavor with #[{}(flavor = \"current_thread\")].", - macro_name + "Set the runtime flavor with #[{macro_name}(flavor = \"current_thread\")]." ) } "flavor" | "worker_threads" | "start_paused" | "crate" | "unhandled_panic" => { - format!("The `{}` attribute requires an argument.", name) + format!("The `{name}` attribute requires an argument.") } name => { - format!("Unknown attribute {} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.", name) + format!("Unknown attribute {name} is specified; expected one of: `flavor`, `worker_threads`, `start_paused`, `crate`, `unhandled_panic`.") } }; return Err(syn::Error::new_spanned(path, msg)); diff --git a/tokio-macros/src/lib.rs b/tokio-macros/src/lib.rs index 29ea2867cff..32353b3807b 100644 --- a/tokio-macros/src/lib.rs +++ b/tokio-macros/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![allow(clippy::needless_doctest_main)] #![warn( missing_debug_implementations, @@ -211,7 +210,6 @@ use proc_macro::TokenStream; /// This option is only compatible with the `current_thread` runtime. /// /// ```no_run -/// # #![allow(unknown_lints, unexpected_cfgs)] /// #[cfg(tokio_unstable)] /// #[tokio::main(flavor = "current_thread", unhandled_panic = "shutdown_runtime")] /// async fn main() { @@ -226,7 +224,6 @@ use proc_macro::TokenStream; /// Equivalent code not using `#[tokio::main]` /// /// ```no_run -/// # #![allow(unknown_lints, unexpected_cfgs)] /// #[cfg(tokio_unstable)] /// fn main() { /// tokio::runtime::Builder::new_current_thread() @@ -480,7 +477,6 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream { /// This option is only compatible with the `current_thread` runtime. /// /// ```no_run -/// # #![allow(unknown_lints, unexpected_cfgs)] /// #[cfg(tokio_unstable)] /// #[tokio::test(flavor = "current_thread", unhandled_panic = "shutdown_runtime")] /// async fn my_test() { @@ -495,7 +491,6 @@ pub fn main_rt(args: TokenStream, item: TokenStream) -> TokenStream { /// Equivalent code not using `#[tokio::test]` /// /// ```no_run -/// # #![allow(unknown_lints, unexpected_cfgs)] /// #[cfg(tokio_unstable)] /// #[test] /// fn my_test() { diff --git a/tokio-macros/src/select.rs b/tokio-macros/src/select.rs index 324b8f942e3..0ef6cfbee2f 100644 --- a/tokio-macros/src/select.rs +++ b/tokio-macros/src/select.rs @@ -11,7 +11,7 @@ pub(crate) fn declare_output_enum(input: TokenStream) -> TokenStream { }; let variants = (0..branches) - .map(|num| Ident::new(&format!("_{}", num), Span::call_site())) + .map(|num| Ident::new(&format!("_{num}"), Span::call_site())) .collect::>(); // Use a bitfield to track which futures completed diff --git a/tokio-stream/CHANGELOG.md b/tokio-stream/CHANGELOG.md index 8f0d2e30832..ced4edeb45c 100644 --- a/tokio-stream/CHANGELOG.md +++ b/tokio-stream/CHANGELOG.md @@ -1,3 +1,14 @@ +# 0.1.17 (December 6th, 2024) + +- deps: fix dev-dependency on tokio-test ([#6931], [#7019]) +- stream: fix link on `Peekable` ([#6861]) +- sync: fix `Stream` link in broadcast docs ([#6873]) + +[#6861]: https://github.com/tokio-rs/tokio/pull/6861 +[#6873]: https://github.com/tokio-rs/tokio/pull/6873 +[#6931]: https://github.com/tokio-rs/tokio/pull/6931 +[#7019]: https://github.com/tokio-rs/tokio/pull/7019 + # 0.1.16 (September 5th, 2024) This release bumps the MSRV of tokio-stream to 1.70. diff --git a/tokio-stream/Cargo.toml b/tokio-stream/Cargo.toml index 0b3244f7148..547d7f5deaf 100644 --- a/tokio-stream/Cargo.toml +++ b/tokio-stream/Cargo.toml @@ -4,7 +4,7 @@ name = "tokio-stream" # - Remove path dependencies # - Update CHANGELOG.md. # - Create "tokio-stream-0.1.x" git tag. -version = "0.1.16" +version = "0.1.17" edition = "2021" rust-version = "1.70" authors = ["Tokio Contributors "] @@ -56,3 +56,6 @@ rustdoc-args = ["--cfg", "docsrs"] # This should allow `docsrs` to be read across projects, so that `tokio-stream` # can pick up stubbed types exported by `tokio`. rustc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true diff --git a/tokio-stream/src/lib.rs b/tokio-stream/src/lib.rs index f2b463bcb9a..28fa22a2ff6 100644 --- a/tokio-stream/src/lib.rs +++ b/tokio-stream/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![allow( clippy::cognitive_complexity, clippy::large_enum_variant, diff --git a/tokio-stream/src/wrappers/broadcast.rs b/tokio-stream/src/wrappers/broadcast.rs index b3900db8ff6..3474cff7718 100644 --- a/tokio-stream/src/wrappers/broadcast.rs +++ b/tokio-stream/src/wrappers/broadcast.rs @@ -10,6 +10,29 @@ use std::task::{ready, Context, Poll}; /// A wrapper around [`tokio::sync::broadcast::Receiver`] that implements [`Stream`]. /// +/// # Example +/// +/// ``` +/// use tokio::sync::broadcast; +/// use tokio_stream::wrappers::BroadcastStream; +/// use tokio_stream::StreamExt; +/// +/// # #[tokio::main(flavor = "current_thread")] +/// # async fn main() -> Result<(), tokio::sync::broadcast::error::SendError> { +/// let (tx, rx) = broadcast::channel(16); +/// tx.send(10)?; +/// tx.send(20)?; +/// # // prevent the doc test from hanging +/// drop(tx); +/// +/// let mut stream = BroadcastStream::new(rx); +/// assert_eq!(stream.next().await, Some(Ok(10))); +/// assert_eq!(stream.next().await, Some(Ok(20))); +/// assert_eq!(stream.next().await, None); +/// # Ok(()) +/// # } +/// ``` +/// /// [`tokio::sync::broadcast::Receiver`]: struct@tokio::sync::broadcast::Receiver /// [`Stream`]: trait@futures_core::Stream #[cfg_attr(docsrs, doc(cfg(feature = "sync")))] diff --git a/tokio-stream/src/wrappers/interval.rs b/tokio-stream/src/wrappers/interval.rs index c7a0b1f1e2a..faac5e78a3e 100644 --- a/tokio-stream/src/wrappers/interval.rs +++ b/tokio-stream/src/wrappers/interval.rs @@ -5,6 +5,26 @@ use tokio::time::{Instant, Interval}; /// A wrapper around [`Interval`] that implements [`Stream`]. /// +/// # Example +/// +/// ``` +/// use tokio::time::{Duration, Instant, interval}; +/// use tokio_stream::wrappers::IntervalStream; +/// use tokio_stream::StreamExt; +/// +/// # #[tokio::main(flavor = "current_thread")] +/// # async fn main() { +/// let start = Instant::now(); +/// let interval = interval(Duration::from_millis(10)); +/// let mut stream = IntervalStream::new(interval); +/// for _ in 0..3 { +/// if let Some(instant) = stream.next().await { +/// println!("elapsed: {:.1?}", instant.duration_since(start)); +/// } +/// } +/// # } +/// ``` +/// /// [`Interval`]: struct@tokio::time::Interval /// [`Stream`]: trait@crate::Stream #[derive(Debug)] diff --git a/tokio-stream/src/wrappers/lines.rs b/tokio-stream/src/wrappers/lines.rs index 4850429a72d..57b41fbc736 100644 --- a/tokio-stream/src/wrappers/lines.rs +++ b/tokio-stream/src/wrappers/lines.rs @@ -8,6 +8,24 @@ use tokio::io::{AsyncBufRead, Lines}; pin_project! { /// A wrapper around [`tokio::io::Lines`] that implements [`Stream`]. /// + /// # Example + /// + /// ``` + /// use tokio::io::AsyncBufReadExt; + /// use tokio_stream::wrappers::LinesStream; + /// use tokio_stream::StreamExt; + /// + /// # #[tokio::main(flavor = "current_thread")] + /// # async fn main() -> std::io::Result<()> { + /// let input = b"Hello\nWorld\n"; + /// let mut stream = LinesStream::new(input.lines()); + /// while let Some(line) = stream.next().await { + /// println!("{}", line?); + /// } + /// # Ok(()) + /// # } + /// ``` + /// /// [`tokio::io::Lines`]: struct@tokio::io::Lines /// [`Stream`]: trait@crate::Stream #[derive(Debug)] diff --git a/tokio-stream/src/wrappers/mpsc_bounded.rs b/tokio-stream/src/wrappers/mpsc_bounded.rs index 18d799e98b1..34b2e020d75 100644 --- a/tokio-stream/src/wrappers/mpsc_bounded.rs +++ b/tokio-stream/src/wrappers/mpsc_bounded.rs @@ -5,6 +5,29 @@ use tokio::sync::mpsc::Receiver; /// A wrapper around [`tokio::sync::mpsc::Receiver`] that implements [`Stream`]. /// +/// # Example +/// +/// ``` +/// use tokio::sync::mpsc; +/// use tokio_stream::wrappers::ReceiverStream; +/// use tokio_stream::StreamExt; +/// +/// # #[tokio::main(flavor = "current_thread")] +/// # async fn main() -> Result<(), tokio::sync::mpsc::error::SendError> { +/// let (tx, rx) = mpsc::channel(2); +/// tx.send(10).await?; +/// tx.send(20).await?; +/// # // prevent the doc test from hanging +/// drop(tx); +/// +/// let mut stream = ReceiverStream::new(rx); +/// assert_eq!(stream.next().await, Some(10)); +/// assert_eq!(stream.next().await, Some(20)); +/// assert_eq!(stream.next().await, None); +/// # Ok(()) +/// # } +/// ``` +/// /// [`tokio::sync::mpsc::Receiver`]: struct@tokio::sync::mpsc::Receiver /// [`Stream`]: trait@crate::Stream #[derive(Debug)] diff --git a/tokio-stream/src/wrappers/mpsc_unbounded.rs b/tokio-stream/src/wrappers/mpsc_unbounded.rs index 6945b08717c..904c98bef95 100644 --- a/tokio-stream/src/wrappers/mpsc_unbounded.rs +++ b/tokio-stream/src/wrappers/mpsc_unbounded.rs @@ -5,6 +5,29 @@ use tokio::sync::mpsc::UnboundedReceiver; /// A wrapper around [`tokio::sync::mpsc::UnboundedReceiver`] that implements [`Stream`]. /// +/// # Example +/// +/// ``` +/// use tokio::sync::mpsc; +/// use tokio_stream::wrappers::UnboundedReceiverStream; +/// use tokio_stream::StreamExt; +/// +/// # #[tokio::main(flavor = "current_thread")] +/// # async fn main() -> Result<(), tokio::sync::mpsc::error::SendError> { +/// let (tx, rx) = mpsc::unbounded_channel(); +/// tx.send(10)?; +/// tx.send(20)?; +/// # // prevent the doc test from hanging +/// drop(tx); +/// +/// let mut stream = UnboundedReceiverStream::new(rx); +/// assert_eq!(stream.next().await, Some(10)); +/// assert_eq!(stream.next().await, Some(20)); +/// assert_eq!(stream.next().await, None); +/// # Ok(()) +/// # } +/// ``` +/// /// [`tokio::sync::mpsc::UnboundedReceiver`]: struct@tokio::sync::mpsc::UnboundedReceiver /// [`Stream`]: trait@crate::Stream #[derive(Debug)] diff --git a/tokio-stream/src/wrappers/read_dir.rs b/tokio-stream/src/wrappers/read_dir.rs index b5cf54f79e1..21522c03d2d 100644 --- a/tokio-stream/src/wrappers/read_dir.rs +++ b/tokio-stream/src/wrappers/read_dir.rs @@ -6,6 +6,24 @@ use tokio::fs::{DirEntry, ReadDir}; /// A wrapper around [`tokio::fs::ReadDir`] that implements [`Stream`]. /// +/// # Example +/// +/// ``` +/// use tokio::fs::read_dir; +/// use tokio_stream::{StreamExt, wrappers::ReadDirStream}; +/// +/// # #[tokio::main(flavor = "current_thread")] +/// # async fn main() -> std::io::Result<()> { +/// let dirs = read_dir(".").await?; +/// let mut dirs = ReadDirStream::new(dirs); +/// while let Some(dir) = dirs.next().await { +/// let dir = dir?; +/// println!("{}", dir.path().display()); +/// } +/// # Ok(()) +/// # } +/// ``` +/// /// [`tokio::fs::ReadDir`]: struct@tokio::fs::ReadDir /// [`Stream`]: trait@crate::Stream #[derive(Debug)] diff --git a/tokio-stream/src/wrappers/signal_unix.rs b/tokio-stream/src/wrappers/signal_unix.rs index 6dcdff7fc55..ac873f92b3f 100644 --- a/tokio-stream/src/wrappers/signal_unix.rs +++ b/tokio-stream/src/wrappers/signal_unix.rs @@ -5,6 +5,22 @@ use tokio::signal::unix::Signal; /// A wrapper around [`Signal`] that implements [`Stream`]. /// +/// # Example +/// +/// ```no_run +/// use tokio::signal::unix::{signal, SignalKind}; +/// use tokio_stream::{StreamExt, wrappers::SignalStream}; +/// +/// # #[tokio::main(flavor = "current_thread")] +/// # async fn main() -> std::io::Result<()> { +/// let signals = signal(SignalKind::hangup())?; +/// let mut stream = SignalStream::new(signals); +/// while stream.next().await.is_some() { +/// println!("hangup signal received"); +/// } +/// # Ok(()) +/// # } +/// ``` /// [`Signal`]: struct@tokio::signal::unix::Signal /// [`Stream`]: trait@crate::Stream #[derive(Debug)] diff --git a/tokio-stream/src/wrappers/signal_windows.rs b/tokio-stream/src/wrappers/signal_windows.rs index 4631fbad8dc..168ed181d34 100644 --- a/tokio-stream/src/wrappers/signal_windows.rs +++ b/tokio-stream/src/wrappers/signal_windows.rs @@ -7,6 +7,23 @@ use tokio::signal::windows::{CtrlBreak, CtrlC}; /// /// [`CtrlC`]: struct@tokio::signal::windows::CtrlC /// [`Stream`]: trait@crate::Stream +/// +/// # Example +/// +/// ```no_run +/// use tokio::signal::windows::ctrl_c; +/// use tokio_stream::{StreamExt, wrappers::CtrlCStream}; +/// +/// # #[tokio::main(flavor = "current_thread")] +/// # async fn main() -> std::io::Result<()> { +/// let signals = ctrl_c()?; +/// let mut stream = CtrlCStream::new(signals); +/// while stream.next().await.is_some() { +/// println!("ctrl-c received"); +/// } +/// # Ok(()) +/// # } +/// ``` #[derive(Debug)] #[cfg_attr(docsrs, doc(cfg(all(windows, feature = "signal"))))] pub struct CtrlCStream { @@ -47,6 +64,23 @@ impl AsMut for CtrlCStream { /// A wrapper around [`CtrlBreak`] that implements [`Stream`]. /// +/// # Example +/// +/// ```no_run +/// use tokio::signal::windows::ctrl_break; +/// use tokio_stream::{StreamExt, wrappers::CtrlBreakStream}; +/// +/// # #[tokio::main(flavor = "current_thread")] +/// # async fn main() -> std::io::Result<()> { +/// let signals = ctrl_break()?; +/// let mut stream = CtrlBreakStream::new(signals); +/// while stream.next().await.is_some() { +/// println!("ctrl-break received"); +/// } +/// # Ok(()) +/// # } +/// ``` +/// /// [`CtrlBreak`]: struct@tokio::signal::windows::CtrlBreak /// [`Stream`]: trait@crate::Stream #[derive(Debug)] diff --git a/tokio-stream/src/wrappers/split.rs b/tokio-stream/src/wrappers/split.rs index ac46a8ba6ff..5d6d77b6787 100644 --- a/tokio-stream/src/wrappers/split.rs +++ b/tokio-stream/src/wrappers/split.rs @@ -8,6 +8,24 @@ use tokio::io::{AsyncBufRead, Split}; pin_project! { /// A wrapper around [`tokio::io::Split`] that implements [`Stream`]. /// + /// # Example + /// + /// ``` + /// use tokio::io::AsyncBufReadExt; + /// use tokio_stream::{StreamExt, wrappers::SplitStream}; + /// + /// # #[tokio::main(flavor = "current_thread")] + /// # async fn main() -> std::io::Result<()> { + /// let input = "Hello\nWorld\n".as_bytes(); + /// let lines = AsyncBufReadExt::split(input, b'\n'); + /// + /// let mut stream = SplitStream::new(lines); + /// while let Some(line) = stream.next().await { + /// println!("length = {}", line?.len()) + /// } + /// # Ok(()) + /// # } + /// ``` /// [`tokio::io::Split`]: struct@tokio::io::Split /// [`Stream`]: trait@crate::Stream #[derive(Debug)] diff --git a/tokio-stream/src/wrappers/tcp_listener.rs b/tokio-stream/src/wrappers/tcp_listener.rs index ce7cb163507..c463ef1426c 100644 --- a/tokio-stream/src/wrappers/tcp_listener.rs +++ b/tokio-stream/src/wrappers/tcp_listener.rs @@ -6,6 +6,33 @@ use tokio::net::{TcpListener, TcpStream}; /// A wrapper around [`TcpListener`] that implements [`Stream`]. /// +/// # Example +/// +/// Accept connections from both IPv4 and IPv6 listeners in the same loop: +/// +/// ```no_run +/// use std::net::{Ipv4Addr, Ipv6Addr}; +/// +/// use tokio::net::TcpListener; +/// use tokio_stream::{StreamExt, wrappers::TcpListenerStream}; +/// +/// # #[tokio::main(flavor = "current_thread")] +/// # async fn main() -> std::io::Result<()> { +/// let ipv4_listener = TcpListener::bind((Ipv6Addr::LOCALHOST, 8080)).await?; +/// let ipv6_listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 8080)).await?; +/// let ipv4_connections = TcpListenerStream::new(ipv4_listener); +/// let ipv6_connections = TcpListenerStream::new(ipv6_listener); +/// +/// let mut connections = ipv4_connections.chain(ipv6_connections); +/// while let Some(tcp_stream) = connections.next().await { +/// let stream = tcp_stream?; +/// let peer_addr = stream.peer_addr()?; +/// println!("accepted connection; peer address = {peer_addr}"); +/// } +/// # Ok(()) +/// # } +/// ``` +/// /// [`TcpListener`]: struct@tokio::net::TcpListener /// [`Stream`]: trait@crate::Stream #[derive(Debug)] diff --git a/tokio-stream/src/wrappers/unix_listener.rs b/tokio-stream/src/wrappers/unix_listener.rs index 0beba588c20..6c4cd43eb12 100644 --- a/tokio-stream/src/wrappers/unix_listener.rs +++ b/tokio-stream/src/wrappers/unix_listener.rs @@ -6,6 +6,25 @@ use tokio::net::{UnixListener, UnixStream}; /// A wrapper around [`UnixListener`] that implements [`Stream`]. /// +/// # Example +/// +/// ```no_run +/// use tokio::net::UnixListener; +/// use tokio_stream::{StreamExt, wrappers::UnixListenerStream}; +/// +/// # #[tokio::main(flavor = "current_thread")] +/// # async fn main() -> std::io::Result<()> { +/// let listener = UnixListener::bind("/tmp/sock")?; +/// let mut incoming = UnixListenerStream::new(listener); +/// +/// while let Some(stream) = incoming.next().await { +/// let stream = stream?; +/// let peer_addr = stream.peer_addr()?; +/// println!("Accepted connection from: {peer_addr:?}"); +/// } +/// # Ok(()) +/// # } +/// ``` /// [`UnixListener`]: struct@tokio::net::UnixListener /// [`Stream`]: trait@crate::Stream #[derive(Debug)] diff --git a/tokio-stream/tests/stream_chain.rs b/tokio-stream/tests/stream_chain.rs index 4b47ce7a905..bae0efb5d3e 100644 --- a/tokio-stream/tests/stream_chain.rs +++ b/tokio-stream/tests/stream_chain.rs @@ -49,6 +49,7 @@ where } #[tokio::test] +#[cfg_attr(miri, ignore)] // Block on https://github.com/tokio-rs/tokio/issues/6860 async fn pending_first() { let (tx1, rx1) = mpsc::unbounded_channel_stream(); let (tx2, rx2) = mpsc::unbounded_channel_stream(); diff --git a/tokio-test/Cargo.toml b/tokio-test/Cargo.toml index 536c5a848e8..c8d998fd368 100644 --- a/tokio-test/Cargo.toml +++ b/tokio-test/Cargo.toml @@ -30,3 +30,6 @@ futures-util = "0.3.0" [package.metadata.docs.rs] all-features = true + +[lints] +workspace = true diff --git a/tokio-test/src/lib.rs b/tokio-test/src/lib.rs index 9f60faf7952..87e63861210 100644 --- a/tokio-test/src/lib.rs +++ b/tokio-test/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![warn( missing_debug_implementations, missing_docs, diff --git a/tokio-test/src/stream_mock.rs b/tokio-test/src/stream_mock.rs index 1a8cf04df1b..50808596ac4 100644 --- a/tokio-test/src/stream_mock.rs +++ b/tokio-test/src/stream_mock.rs @@ -161,8 +161,7 @@ impl Drop for StreamMock { assert!( undropped_count == 0, - "StreamMock was dropped before all actions were consumed, {} actions were not consumed", - undropped_count + "StreamMock was dropped before all actions were consumed, {undropped_count} actions were not consumed" ); } } diff --git a/tokio-test/tests/io.rs b/tokio-test/tests/io.rs index 5f2ed427cd3..effac9a51fa 100644 --- a/tokio-test/tests/io.rs +++ b/tokio-test/tests/io.rs @@ -34,7 +34,7 @@ async fn read_error() { match mock.read(&mut buf).await { Err(error) => { assert_eq!(error.kind(), io::ErrorKind::Other); - assert_eq!("cruel", format!("{}", error)); + assert_eq!("cruel", format!("{error}")); } Ok(_) => panic!("error not received"), } @@ -87,7 +87,7 @@ async fn write_error() { match mock.write_all(b"whoa").await { Err(error) => { assert_eq!(error.kind(), io::ErrorKind::Other); - assert_eq!("cruel", format!("{}", error)); + assert_eq!("cruel", format!("{error}")); } Ok(_) => panic!("error not received"), } diff --git a/tokio-util/CHANGELOG.md b/tokio-util/CHANGELOG.md index db589136bab..28314bba145 100644 --- a/tokio-util/CHANGELOG.md +++ b/tokio-util/CHANGELOG.md @@ -1,3 +1,11 @@ +# 0.7.13 (December 4th, 2024) + +### Fixed + +- codec: fix incorrect handling of invalid utf-8 in `LinesCodec::decode_eof` ([#7011]) + +[#7011]: https://github.com/tokio-rs/tokio/pull/7011 + # 0.7.12 (September 5th, 2024) This release bumps the MSRV to 1.70. ([#6645]) @@ -127,7 +135,7 @@ This release contains one performance improvement: [#5630]: https://github.com/tokio-rs/tokio/pull/5630 [#5632]: https://github.com/tokio-rs/tokio/pull/5632 -# 0.7.7 (February 12, 2023) +# 0.7.7 (February 12th, 2023) This release reverts the removal of the `Encoder` bound on the `FramedParts` constructor from [#5280] since it turned out to be a breaking change. ([#5450]) diff --git a/tokio-util/Cargo.toml b/tokio-util/Cargo.toml index 12f70be862e..b5a93dc3b50 100644 --- a/tokio-util/Cargo.toml +++ b/tokio-util/Cargo.toml @@ -4,7 +4,7 @@ name = "tokio-util" # - Remove path dependencies # - Update CHANGELOG.md. # - Create "tokio-util-0.7.x" git tag. -version = "0.7.12" +version = "0.7.13" edition = "2021" rust-version = "1.70" authors = ["Tokio Contributors "] @@ -68,3 +68,6 @@ rustc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable"] [package.metadata.playground] features = ["full"] + +[lints] +workspace = true diff --git a/tokio-util/src/codec/any_delimiter_codec.rs b/tokio-util/src/codec/any_delimiter_codec.rs index fc5e57582db..ad012252b3e 100644 --- a/tokio-util/src/codec/any_delimiter_codec.rs +++ b/tokio-util/src/codec/any_delimiter_codec.rs @@ -249,7 +249,7 @@ impl fmt::Display for AnyDelimiterCodecError { AnyDelimiterCodecError::MaxChunkLengthExceeded => { write!(f, "max chunk length exceeded") } - AnyDelimiterCodecError::Io(e) => write!(f, "{}", e), + AnyDelimiterCodecError::Io(e) => write!(f, "{e}"), } } } diff --git a/tokio-util/src/codec/lines_codec.rs b/tokio-util/src/codec/lines_codec.rs index 0da19238b63..cc1ac4ebdad 100644 --- a/tokio-util/src/codec/lines_codec.rs +++ b/tokio-util/src/codec/lines_codec.rs @@ -169,6 +169,7 @@ impl Decoder for LinesCodec { Ok(match self.decode(buf)? { Some(frame) => Some(frame), None => { + self.next_index = 0; // No terminating newline - return remaining data, if any if buf.is_empty() || buf == &b"\r"[..] { None @@ -176,7 +177,6 @@ impl Decoder for LinesCodec { let line = buf.split_to(buf.len()); let line = without_carriage_return(&line); let line = utf8(line)?; - self.next_index = 0; Some(line.to_string()) } } @@ -218,7 +218,7 @@ impl fmt::Display for LinesCodecError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { LinesCodecError::MaxLineLengthExceeded => write!(f, "max line length exceeded"), - LinesCodecError::Io(e) => write!(f, "{}", e), + LinesCodecError::Io(e) => write!(f, "{e}"), } } } diff --git a/tokio-util/src/codec/mod.rs b/tokio-util/src/codec/mod.rs index 50f01c28fa7..11f44133e69 100644 --- a/tokio-util/src/codec/mod.rs +++ b/tokio-util/src/codec/mod.rs @@ -224,7 +224,7 @@ //! The main method on the `Encoder` trait is the [`encode`] method. This method //! takes an item that is being written, and a buffer to write the item to. The //! buffer may already contain data, and in this case, the encoder should append -//! the new frame the to buffer rather than overwrite the existing data. +//! the new frame to the buffer rather than overwrite the existing data. //! //! It is guaranteed that, from one call to `encode` to another, the provided //! buffer will contain the exact same data as before, except that some of the diff --git a/tokio-util/src/either.rs b/tokio-util/src/either.rs index e7fec9546b3..f661bb9e11d 100644 --- a/tokio-util/src/either.rs +++ b/tokio-util/src/either.rs @@ -150,6 +150,21 @@ where fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { delegate_call!(self.poll_shutdown(cx)) } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[std::io::IoSlice<'_>], + ) -> Poll> { + delegate_call!(self.poll_write_vectored(cx, bufs)) + } + + fn is_write_vectored(&self) -> bool { + match self { + Self::Left(l) => l.is_write_vectored(), + Self::Right(r) => r.is_write_vectored(), + } + } } impl futures_core::stream::Stream for Either diff --git a/tokio-util/src/io/sync_bridge.rs b/tokio-util/src/io/sync_bridge.rs index 2402207584c..ce3d598eaa2 100644 --- a/tokio-util/src/io/sync_bridge.rs +++ b/tokio-util/src/io/sync_bridge.rs @@ -154,3 +154,15 @@ impl SyncIoBridge { self.src } } + +impl AsMut for SyncIoBridge { + fn as_mut(&mut self) -> &mut T { + &mut self.src + } +} + +impl AsRef for SyncIoBridge { + fn as_ref(&self) -> &T { + &self.src + } +} diff --git a/tokio-util/src/lib.rs b/tokio-util/src/lib.rs index 34f69fd14e3..1df4de1b459 100644 --- a/tokio-util/src/lib.rs +++ b/tokio-util/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![allow(clippy::needless_doctest_main)] #![warn( missing_debug_implementations, diff --git a/tokio-util/src/task/spawn_pinned.rs b/tokio-util/src/task/spawn_pinned.rs index c94d5e8411b..177ea028df3 100644 --- a/tokio-util/src/task/spawn_pinned.rs +++ b/tokio-util/src/task/spawn_pinned.rs @@ -39,9 +39,9 @@ use tokio::task::{spawn_local, JoinHandle, LocalSet}; /// task::spawn_local(async move { /// println!("{}", data_clone); /// }); -/// +/// /// data.to_string() -/// } +/// } /// }).await.unwrap(); /// println!("output: {}", output); /// } @@ -249,7 +249,7 @@ impl LocalPool { // Send the callback to the LocalSet task if let Err(e) = worker_spawner.send(spawn_task) { // Propagate the error as a panic in the join handle. - panic!("Failed to send job to worker: {}", e); + panic!("Failed to send job to worker: {e}"); } // Wait for the task's join handle @@ -260,7 +260,7 @@ impl LocalPool { // join handle... We assume something happened to the worker // and the task was not spawned. Propagate the error as a // panic in the join handle. - panic!("Worker failed to send join handle: {}", e); + panic!("Worker failed to send join handle: {e}"); } }; @@ -284,12 +284,12 @@ impl LocalPool { // No one else should have the join handle, so this is // unexpected. Forward this error as a panic in the join // handle. - panic!("spawn_pinned task was canceled: {}", e); + panic!("spawn_pinned task was canceled: {e}"); } else { // Something unknown happened (not a panic or // cancellation). Forward this error as a panic in the // join handle. - panic!("spawn_pinned task failed: {}", e); + panic!("spawn_pinned task failed: {e}"); } } } diff --git a/tokio-util/src/time/delay_queue.rs b/tokio-util/src/time/delay_queue.rs index 9dadc3f00ae..a65101d163a 100644 --- a/tokio-util/src/time/delay_queue.rs +++ b/tokio-util/src/time/delay_queue.rs @@ -665,7 +665,7 @@ impl DelayQueue { // The delay is already expired, store it in the expired queue self.expired.push(key, &mut self.slab); } - Err((_, err)) => panic!("invalid deadline; err={:?}", err), + Err((_, err)) => panic!("invalid deadline; err={err:?}"), } } diff --git a/tokio-util/src/time/wheel/level.rs b/tokio-util/src/time/wheel/level.rs index a69aee918aa..19be388e783 100644 --- a/tokio-util/src/time/wheel/level.rs +++ b/tokio-util/src/time/wheel/level.rs @@ -39,86 +39,10 @@ const LEVEL_MULT: usize = 64; impl Level { pub(crate) fn new(level: usize) -> Level { - // Rust's derived implementations for arrays require that the value - // contained by the array be `Copy`. So, here we have to manually - // initialize every single slot. - macro_rules! s { - () => { - T::default() - }; - } - Level { level, occupied: 0, - slot: [ - // It does not look like the necessary traits are - // derived for [T; 64]. - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - s!(), - ], + slot: std::array::from_fn(|_| T::default()), } } diff --git a/tokio-util/src/time/wheel/mod.rs b/tokio-util/src/time/wheel/mod.rs index 04e6d3a4262..c2c87a67627 100644 --- a/tokio-util/src/time/wheel/mod.rs +++ b/tokio-util/src/time/wheel/mod.rs @@ -280,13 +280,7 @@ mod test { #[test] fn test_level_for() { for pos in 0..64 { - assert_eq!( - 0, - level_for(0, pos), - "level_for({}) -- binary = {:b}", - pos, - pos - ); + assert_eq!(0, level_for(0, pos), "level_for({pos}) -- binary = {pos:b}"); } for level in 1..5 { @@ -295,9 +289,7 @@ mod test { assert_eq!( level, level_for(0, a as u64), - "level_for({}) -- binary = {:b}", - a, - a + "level_for({a}) -- binary = {a:b}" ); if pos > level { @@ -305,9 +297,7 @@ mod test { assert_eq!( level, level_for(0, a as u64), - "level_for({}) -- binary = {:b}", - a, - a + "level_for({a}) -- binary = {a:b}" ); } @@ -316,9 +306,7 @@ mod test { assert_eq!( level, level_for(0, a as u64), - "level_for({}) -- binary = {:b}", - a, - a + "level_for({a}) -- binary = {a:b}" ); } } diff --git a/tokio-util/tests/codecs.rs b/tokio-util/tests/codecs.rs index f9a780140a2..57743be7155 100644 --- a/tokio-util/tests/codecs.rs +++ b/tokio-util/tests/codecs.rs @@ -62,6 +62,19 @@ fn lines_decoder() { assert_eq!(None, codec.decode_eof(buf).unwrap()); } +#[test] +fn lines_decoder_invalid_utf8() { + let mut codec = LinesCodec::new(); + let buf = &mut BytesMut::new(); + buf.reserve(200); + buf.put_slice(b"line 1\xc3\x28"); + assert_eq!(None, codec.decode(buf).unwrap()); + assert!(codec.decode_eof(buf).is_err()); + assert_eq!(None, codec.decode_eof(buf).unwrap()); + buf.put_slice(b"line 22222222222222\n"); + assert_eq!("line 22222222222222", codec.decode(buf).unwrap().unwrap()); +} + #[test] fn lines_decoder_max_length() { const MAX_LENGTH: usize = 6; @@ -75,32 +88,17 @@ fn lines_decoder_max_length() { assert!(codec.decode(buf).is_err()); let line = codec.decode(buf).unwrap().unwrap(); - assert!( - line.len() <= MAX_LENGTH, - "{:?}.len() <= {:?}", - line, - MAX_LENGTH - ); + assert!(line.len() <= MAX_LENGTH, "{line:?}.len() <= {MAX_LENGTH:?}"); assert_eq!("line 2", line); assert!(codec.decode(buf).is_err()); let line = codec.decode(buf).unwrap().unwrap(); - assert!( - line.len() <= MAX_LENGTH, - "{:?}.len() <= {:?}", - line, - MAX_LENGTH - ); + assert!(line.len() <= MAX_LENGTH, "{line:?}.len() <= {MAX_LENGTH:?}"); assert_eq!("line 4", line); let line = codec.decode(buf).unwrap().unwrap(); - assert!( - line.len() <= MAX_LENGTH, - "{:?}.len() <= {:?}", - line, - MAX_LENGTH - ); + assert!(line.len() <= MAX_LENGTH, "{line:?}.len() <= {MAX_LENGTH:?}"); assert_eq!("", line); assert_eq!(None, codec.decode(buf).unwrap()); @@ -109,12 +107,7 @@ fn lines_decoder_max_length() { assert_eq!(None, codec.decode(buf).unwrap()); let line = codec.decode_eof(buf).unwrap().unwrap(); - assert!( - line.len() <= MAX_LENGTH, - "{:?}.len() <= {:?}", - line, - MAX_LENGTH - ); + assert!(line.len() <= MAX_LENGTH, "{line:?}.len() <= {MAX_LENGTH:?}"); assert_eq!("\rk", line); assert_eq!(None, codec.decode(buf).unwrap()); @@ -273,18 +266,14 @@ fn any_delimiters_decoder_max_length() { let chunk = codec.decode(buf).unwrap().unwrap(); assert!( chunk.len() <= MAX_LENGTH, - "{:?}.len() <= {:?}", - chunk, - MAX_LENGTH + "{chunk:?}.len() <= {MAX_LENGTH:?}" ); assert_eq!("chunk 2", chunk); let chunk = codec.decode(buf).unwrap().unwrap(); assert!( chunk.len() <= MAX_LENGTH, - "{:?}.len() <= {:?}", - chunk, - MAX_LENGTH + "{chunk:?}.len() <= {MAX_LENGTH:?}" ); assert_eq!("chunk 3", chunk); @@ -292,36 +281,28 @@ fn any_delimiters_decoder_max_length() { let chunk = codec.decode(buf).unwrap().unwrap(); assert!( chunk.len() <= MAX_LENGTH, - "{:?}.len() <= {:?}", - chunk, - MAX_LENGTH + "{chunk:?}.len() <= {MAX_LENGTH:?}" ); assert_eq!("", chunk); let chunk = codec.decode(buf).unwrap().unwrap(); assert!( chunk.len() <= MAX_LENGTH, - "{:?}.len() <= {:?}", - chunk, - MAX_LENGTH + "{chunk:?}.len() <= {MAX_LENGTH:?}" ); assert_eq!("chunk 4", chunk); let chunk = codec.decode(buf).unwrap().unwrap(); assert!( chunk.len() <= MAX_LENGTH, - "{:?}.len() <= {:?}", - chunk, - MAX_LENGTH + "{chunk:?}.len() <= {MAX_LENGTH:?}" ); assert_eq!("", chunk); let chunk = codec.decode(buf).unwrap().unwrap(); assert!( chunk.len() <= MAX_LENGTH, - "{:?}.len() <= {:?}", - chunk, - MAX_LENGTH + "{chunk:?}.len() <= {MAX_LENGTH:?}" ); assert_eq!("", chunk); @@ -333,9 +314,7 @@ fn any_delimiters_decoder_max_length() { let chunk = codec.decode_eof(buf).unwrap().unwrap(); assert!( chunk.len() <= MAX_LENGTH, - "{:?}.len() <= {:?}", - chunk, - MAX_LENGTH + "{chunk:?}.len() <= {MAX_LENGTH:?}" ); assert_eq!("k", chunk); diff --git a/tokio-util/tests/framed_write.rs b/tokio-util/tests/framed_write.rs index 39091c0b1b5..a827114d5ea 100644 --- a/tokio-util/tests/framed_write.rs +++ b/tokio-util/tests/framed_write.rs @@ -180,7 +180,7 @@ impl Write for Mock { Ok(data.len()) } Some(Err(e)) => Err(e), - None => panic!("unexpected write; {:?}", src), + None => panic!("unexpected write; {src:?}"), } } diff --git a/tokio-util/tests/io_reader_stream.rs b/tokio-util/tests/io_reader_stream.rs index e30cd85164c..35c88209d6d 100644 --- a/tokio-util/tests/io_reader_stream.rs +++ b/tokio-util/tests/io_reader_stream.rs @@ -42,7 +42,7 @@ async fn correct_behavior_on_errors() { let mut had_error = false; loop { let item = stream.next().await.unwrap(); - println!("{:?}", item); + println!("{item:?}"); match item { Ok(bytes) => { let bytes = &*bytes; diff --git a/tokio-util/tests/length_delimited.rs b/tokio-util/tests/length_delimited.rs index 091a5b449e4..14d4ab52137 100644 --- a/tokio-util/tests/length_delimited.rs +++ b/tokio-util/tests/length_delimited.rs @@ -789,7 +789,7 @@ impl AsyncWrite for Mock { match self.calls.pop_front() { Some(Poll::Ready(Ok(Op::Data(data)))) => { let len = data.len(); - assert!(src.len() >= len, "expect={:?}; actual={:?}", data, src); + assert!(src.len() >= len, "expect={data:?}; actual={src:?}"); assert_eq!(&data[..], &src[..len]); Poll::Ready(Ok(len)) } diff --git a/tokio-util/tests/spawn_pinned.rs b/tokio-util/tests/spawn_pinned.rs index 110fdab0abc..e05b4095eb4 100644 --- a/tokio-util/tests/spawn_pinned.rs +++ b/tokio-util/tests/spawn_pinned.rs @@ -1,7 +1,5 @@ #![warn(rust_2018_idioms)] #![cfg(not(target_os = "wasi"))] // Wasi doesn't support threads -// Blocked on https://github.com/rust-lang/miri/issues/3911 -#![cfg(not(miri))] use std::rc::Rc; use std::sync::Arc; diff --git a/tokio-util/tests/task_join_map.rs b/tokio-util/tests/task_join_map.rs index 8757f8b5c6e..1ab5f9ba832 100644 --- a/tokio-util/tests/task_join_map.rs +++ b/tokio-util/tests/task_join_map.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![warn(rust_2018_idioms)] #![cfg(all(feature = "rt", tokio_unstable))] diff --git a/tokio-util/tests/time_delay_queue.rs b/tokio-util/tests/time_delay_queue.rs index 855b82dd40e..fdd0844c8c3 100644 --- a/tokio-util/tests/time_delay_queue.rs +++ b/tokio-util/tests/time_delay_queue.rs @@ -111,7 +111,7 @@ async fn multi_delay_at_start() { let start = Instant::now(); for elapsed in 0..1200 { - println!("elapsed: {:?}", elapsed); + println!("elapsed: {elapsed:?}"); let elapsed = elapsed + 1; tokio::time::sleep_until(start + ms(elapsed)).await; @@ -328,7 +328,7 @@ async fn remove_at_timer_wheel_threshold() { let entry = queue.remove(&key1).into_inner(); assert_eq!(entry, "foo"); } - other => panic!("other: {:?}", other), + other => panic!("other: {other:?}"), } } diff --git a/tokio/CHANGELOG.md b/tokio/CHANGELOG.md index da140d415a8..d2cb6a204d3 100644 --- a/tokio/CHANGELOG.md +++ b/tokio/CHANGELOG.md @@ -1,3 +1,84 @@ +# 1.43.0 (Jan 8th, 2025) + +### Added + +- net: add `UdpSocket::peek` methods ([#7068]) +- net: add support for Haiku OS ([#7042]) +- process: add `Command::into_std()` ([#7014]) +- signal: add `SignalKind::info` on illumos ([#6995]) +- signal: add support for realtime signals on illumos ([#7029]) + +### Fixed + +- io: don't call `set_len` before initializing vector in `Blocking` ([#7054]) +- macros: suppress `clippy::needless_return` in `#[tokio::main]` ([#6874]) +- runtime: fix thread parking on WebAssembly ([#7041]) + +### Changes + +- chore: use unsync loads for `unsync_load` ([#7073]) +- io: use `Buf::put_bytes` in `Repeat` read impl ([#7055]) +- task: drop the join waker of a task eagerly ([#6986]) + +### Changes to unstable APIs + +- metrics: improve flexibility of H2Histogram Configuration ([#6963]) +- taskdump: add accessor methods for backtrace ([#6975]) + +### Documented + +- io: clarify `ReadBuf::uninit` allows initialized buffers as well ([#7053]) +- net: fix ambiguity in `TcpStream::try_write_vectored` docs ([#7067]) +- runtime: fix `LocalRuntime` doc links ([#7074]) +- sync: extend documentation for `watch::Receiver::wait_for` ([#7038]) +- sync: fix typos in `OnceCell` docs ([#7047]) + +[#6874]: https://github.com/tokio-rs/tokio/pull/6874 +[#6963]: https://github.com/tokio-rs/tokio/pull/6963 +[#6975]: https://github.com/tokio-rs/tokio/pull/6975 +[#6986]: https://github.com/tokio-rs/tokio/pull/6986 +[#6995]: https://github.com/tokio-rs/tokio/pull/6995 +[#7014]: https://github.com/tokio-rs/tokio/pull/7014 +[#7029]: https://github.com/tokio-rs/tokio/pull/7029 +[#7038]: https://github.com/tokio-rs/tokio/pull/7038 +[#7041]: https://github.com/tokio-rs/tokio/pull/7041 +[#7042]: https://github.com/tokio-rs/tokio/pull/7042 +[#7047]: https://github.com/tokio-rs/tokio/pull/7047 +[#7053]: https://github.com/tokio-rs/tokio/pull/7053 +[#7054]: https://github.com/tokio-rs/tokio/pull/7054 +[#7055]: https://github.com/tokio-rs/tokio/pull/7055 +[#7067]: https://github.com/tokio-rs/tokio/pull/7067 +[#7068]: https://github.com/tokio-rs/tokio/pull/7068 +[#7073]: https://github.com/tokio-rs/tokio/pull/7073 +[#7074]: https://github.com/tokio-rs/tokio/pull/7074 + +# 1.42.0 (Dec 3rd, 2024) + +### Added + +- io: add `AsyncFd::{try_io, try_io_mut}` ([#6967]) + +### Fixed + +- io: avoid `ptr->ref->ptr` roundtrip in RegistrationSet ([#6929]) +- runtime: do not defer `yield_now` inside `block_in_place` ([#6999]) + +### Changes + +- io: simplify io readiness logic ([#6966]) + +### Documented + +- net: fix docs for `tokio::net::unix::{pid_t, gid_t, uid_t}` ([#6791]) +- time: fix a typo in `Instant` docs ([#6982]) + +[#6791]: https://github.com/tokio-rs/tokio/pull/6791 +[#6929]: https://github.com/tokio-rs/tokio/pull/6929 +[#6966]: https://github.com/tokio-rs/tokio/pull/6966 +[#6967]: https://github.com/tokio-rs/tokio/pull/6967 +[#6982]: https://github.com/tokio-rs/tokio/pull/6982 +[#6999]: https://github.com/tokio-rs/tokio/pull/6999 + # 1.41.1 (Nov 7th, 2024) ### Fixed @@ -10,7 +91,7 @@ [#6938]: https://github.com/tokio-rs/tokio/pull/6938 [#6944]: https://github.com/tokio-rs/tokio/pull/6944 -# 1.41.0 (Oct 22th, 2024) +# 1.41.0 (Oct 22nd, 2024) ### Added diff --git a/tokio/Cargo.toml b/tokio/Cargo.toml index ac789e242f9..2b0c1127a71 100644 --- a/tokio/Cargo.toml +++ b/tokio/Cargo.toml @@ -6,7 +6,7 @@ name = "tokio" # - README.md # - Update CHANGELOG.md. # - Create "v1.x.y" git tag. -version = "1.41.1" +version = "1.43.0" edition = "2021" rust-version = "1.70" authors = ["Tokio Contributors "] @@ -86,12 +86,12 @@ test-util = ["rt", "sync", "time"] time = [] [dependencies] -tokio-macros = { version = "~2.4.0", path = "../tokio-macros", optional = true } +tokio-macros = { version = "~2.5.0", path = "../tokio-macros", optional = true } pin-project-lite = "0.2.11" # Everything else is optional... -bytes = { version = "1.0.0", optional = true } +bytes = { version = "1.1.0", optional = true } mio = { version = "1.0.1", optional = true, default-features = false } parking_lot = { version = "0.12.0", optional = true } @@ -109,11 +109,11 @@ tracing = { version = "0.1.29", default-features = false, features = ["std"], op backtrace = { version = "0.3.58" } [target.'cfg(unix)'.dependencies] -libc = { version = "0.2.149", optional = true } +libc = { version = "0.2.168", optional = true } signal-hook-registry = { version = "1.1.1", optional = true } [target.'cfg(unix)'.dev-dependencies] -libc = { version = "0.2.149" } +libc = { version = "0.2.168" } nix = { version = "0.29.0", default-features = false, features = ["aio", "fs", "socket"] } [target.'cfg(windows)'.dependencies.windows-sys] @@ -151,6 +151,9 @@ mio-aio = { version = "0.9.0", features = ["tokio"] } [target.'cfg(loom)'.dev-dependencies] loom = { version = "0.7", features = ["futures", "checkpoint"] } +[target.'cfg(all(tokio_unstable, target_has_atomic = "64"))'.dev-dependencies] +tracing-mock = "= 0.1.0-beta.1" + [package.metadata.docs.rs] all-features = true # enable unstable features in the documentation @@ -170,3 +173,6 @@ allowed_external_types = [ "bytes::buf::buf_mut::BufMut", "tokio_macros::*", ] + +[lints] +workspace = true diff --git a/tokio/README.md b/tokio/README.md index c59566173c3..16b0819fea3 100644 --- a/tokio/README.md +++ b/tokio/README.md @@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml: ```toml [dependencies] -tokio = { version = "1.41.1", features = ["full"] } +tokio = { version = "1.43.0", features = ["full"] } ``` Then, on your main.rs: @@ -78,7 +78,7 @@ async fn main() -> Result<(), Box> { loop { let n = match socket.read(&mut buf).await { // socket closed - Ok(n) if n == 0 => return, + Ok(0) => return, Ok(n) => n, Err(e) => { eprintln!("failed to read from socket; err = {:?}", e); @@ -216,7 +216,6 @@ warrants a patch release with a fix for the bug, it will be backported and released as a new patch release for each LTS minor version. Our current LTS releases are: - * `1.32.x` - LTS release until September 2024. (MSRV 1.63) * `1.36.x` - LTS release until March 2025. (MSRV 1.63) * `1.38.x` - LTS release until July 2025. (MSRV 1.63) @@ -238,6 +237,7 @@ tokio = { version = "~1.32", features = [...] } * `1.18.x` - LTS release until June 2023. * `1.20.x` - LTS release until September 2023. * `1.25.x` - LTS release until March 2024. + * `1.32.x` - LTS release until September 2024. ## License diff --git a/tokio/src/doc/mod.rs b/tokio/src/doc/mod.rs index d0b37416711..fa941af1158 100644 --- a/tokio/src/doc/mod.rs +++ b/tokio/src/doc/mod.rs @@ -24,21 +24,21 @@ pub enum NotDefinedHere {} impl mio::event::Source for NotDefinedHere { fn register( &mut self, - registry: &mio::Registry, - token: mio::Token, - interests: mio::Interest, + _registry: &mio::Registry, + _token: mio::Token, + _interests: mio::Interest, ) -> std::io::Result<()> { Ok(()) } fn reregister( &mut self, - registry: &mio::Registry, - token: mio::Token, - interests: mio::Interest, + _registry: &mio::Registry, + _token: mio::Token, + _interests: mio::Interest, ) -> std::io::Result<()> { Ok(()) } - fn deregister(&mut self, registry: &mio::Registry) -> std::io::Result<()> { + fn deregister(&mut self, _registry: &mio::Registry) -> std::io::Result<()> { Ok(()) } } diff --git a/tokio/src/fs/file.rs b/tokio/src/fs/file.rs index 63dd8af3e98..7847066404d 100644 --- a/tokio/src/fs/file.rs +++ b/tokio/src/fs/file.rs @@ -7,6 +7,7 @@ use crate::io::blocking::{Buf, DEFAULT_MAX_BUF_SIZE}; use crate::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf}; use crate::sync::Mutex; +use std::cmp; use std::fmt; use std::fs::{Metadata, Permissions}; use std::future::Future; @@ -600,11 +601,14 @@ impl AsyncRead for File { return Poll::Ready(Ok(())); } - buf.ensure_capacity_for(dst, me.max_buf_size); let std = me.std.clone(); + let max_buf_size = cmp::min(dst.remaining(), me.max_buf_size); inner.state = State::Busy(spawn_blocking(move || { - let res = buf.read_from(&mut &*std); + // SAFETY: the `Read` implementation of `std` does not + // read from the buffer it is borrowing and correctly + // reports the length of the data written into the buffer. + let res = unsafe { buf.read_from(&mut &*std, max_buf_size) }; (Operation::Read(res), buf) })); } diff --git a/tokio/src/fs/mocks.rs b/tokio/src/fs/mocks.rs index a2ce1cd6ca3..54da6be938c 100644 --- a/tokio/src/fs/mocks.rs +++ b/tokio/src/fs/mocks.rs @@ -129,7 +129,7 @@ impl Future for JoinHandle { match Pin::new(&mut self.rx).poll(cx) { Poll::Ready(Ok(v)) => Poll::Ready(Ok(v)), - Poll::Ready(Err(e)) => panic!("error = {:?}", e), + Poll::Ready(Err(e)) => panic!("error = {e:?}"), Poll::Pending => Poll::Pending, } } diff --git a/tokio/src/io/async_fd.rs b/tokio/src/io/async_fd.rs index 0fda5da4088..8ecc6b95289 100644 --- a/tokio/src/io/async_fd.rs +++ b/tokio/src/io/async_fd.rs @@ -872,6 +872,56 @@ impl AsyncFd { .async_io(interest, || f(self.inner.as_mut().unwrap())) .await } + + /// Tries to read or write from the file descriptor using a user-provided IO operation. + /// + /// If the file descriptor is ready, the provided closure is called. The closure + /// should attempt to perform IO operation on the file descriptor by manually + /// calling the appropriate syscall. If the operation fails because the + /// file descriptor is not actually ready, then the closure should return a + /// `WouldBlock` error and the readiness flag is cleared. The return value + /// of the closure is then returned by `try_io`. + /// + /// If the file descriptor is not ready, then the closure is not called + /// and a `WouldBlock` error is returned. + /// + /// The closure should only return a `WouldBlock` error if it has performed + /// an IO operation on the file descriptor that failed due to the file descriptor not being + /// ready. Returning a `WouldBlock` error in any other situation will + /// incorrectly clear the readiness flag, which can cause the file descriptor to + /// behave incorrectly. + /// + /// The closure should not perform the IO operation using any of the methods + /// defined on the Tokio `AsyncFd` type, as this will mess with the + /// readiness flag and can cause the file descriptor to behave incorrectly. + /// + /// This method is not intended to be used with combined interests. + /// The closure should perform only one type of IO operation, so it should not + /// require more than one ready state. This method may panic or sleep forever + /// if it is called with a combined interest. + pub fn try_io( + &self, + interest: Interest, + f: impl FnOnce(&T) -> io::Result, + ) -> io::Result { + self.registration + .try_io(interest, || f(self.inner.as_ref().unwrap())) + } + + /// Tries to read or write from the file descriptor using a user-provided IO operation. + /// + /// The behavior is the same as [`try_io`], except that the closure can mutate the inner + /// value of the [`AsyncFd`]. + /// + /// [`try_io`]: AsyncFd::try_io + pub fn try_io_mut( + &mut self, + interest: Interest, + f: impl FnOnce(&mut T) -> io::Result, + ) -> io::Result { + self.registration + .try_io(interest, || f(self.inner.as_mut().unwrap())) + } } impl AsRawFd for AsyncFd { diff --git a/tokio/src/io/blocking.rs b/tokio/src/io/blocking.rs index f189136b52e..1af5065456d 100644 --- a/tokio/src/io/blocking.rs +++ b/tokio/src/io/blocking.rs @@ -5,6 +5,7 @@ use std::cmp; use std::future::Future; use std::io; use std::io::prelude::*; +use std::mem::MaybeUninit; use std::pin::Pin; use std::task::{ready, Context, Poll}; @@ -33,8 +34,13 @@ enum State { cfg_io_blocking! { impl Blocking { + /// # Safety + /// + /// The `Read` implementation of `inner` must never read from the buffer + /// it is borrowing and must correctly report the length of the data + /// written into the buffer. #[cfg_attr(feature = "fs", allow(dead_code))] - pub(crate) fn new(inner: T) -> Blocking { + pub(crate) unsafe fn new(inner: T) -> Blocking { Blocking { inner: Some(inner), state: State::Idle(Some(Buf::with_capacity(0))), @@ -64,11 +70,12 @@ where return Poll::Ready(Ok(())); } - buf.ensure_capacity_for(dst, DEFAULT_MAX_BUF_SIZE); let mut inner = self.inner.take().unwrap(); + let max_buf_size = cmp::min(dst.remaining(), DEFAULT_MAX_BUF_SIZE); self.state = State::Busy(sys::run(move || { - let res = buf.read_from(&mut inner); + // SAFETY: the requirements are satisfied by `Blocking::new`. + let res = unsafe { buf.read_from(&mut inner, max_buf_size) }; (res, buf, inner) })); } @@ -227,25 +234,30 @@ impl Buf { &self.buf[self.pos..] } - pub(crate) fn ensure_capacity_for(&mut self, bytes: &ReadBuf<'_>, max_buf_size: usize) { + /// # Safety + /// + /// `rd` must not read from the buffer `read` is borrowing and must correctly + /// report the length of the data written into the buffer. + pub(crate) unsafe fn read_from( + &mut self, + rd: &mut T, + max_buf_size: usize, + ) -> io::Result { assert!(self.is_empty()); + self.buf.reserve(max_buf_size); - let len = cmp::min(bytes.remaining(), max_buf_size); - - if self.buf.len() < len { - self.buf.reserve(len - self.buf.len()); - } - - unsafe { - self.buf.set_len(len); - } - } - - pub(crate) fn read_from(&mut self, rd: &mut T) -> io::Result { - let res = uninterruptibly!(rd.read(&mut self.buf)); + let buf = &mut self.buf.spare_capacity_mut()[..max_buf_size]; + // SAFETY: The memory may be uninitialized, but `rd.read` will only write to the buffer. + let buf = unsafe { &mut *(buf as *mut [MaybeUninit] as *mut [u8]) }; + let res = uninterruptibly!(rd.read(buf)); if let Ok(n) = res { - self.buf.truncate(n); + // SAFETY: the caller promises that `rd.read` initializes + // a section of `buf` and correctly reports that length. + // The `self.is_empty()` assertion verifies that `n` + // equals the length of the `buf` capacity that was written + // to (and that `buf` isn't being shrunk). + unsafe { self.buf.set_len(n) } } else { self.buf.clear(); } diff --git a/tokio/src/io/mod.rs b/tokio/src/io/mod.rs index 99cabde0ab8..dc2c4309e66 100644 --- a/tokio/src/io/mod.rs +++ b/tokio/src/io/mod.rs @@ -231,6 +231,9 @@ cfg_io_driver_impl! { pub(crate) use poll_evented::PollEvented; } +// The bsd module can't be build on Windows, so we completely ignore it, even +// when building documentation. +#[cfg(unix)] cfg_aio! { /// BSD-specific I/O types. pub mod bsd { diff --git a/tokio/src/io/read_buf.rs b/tokio/src/io/read_buf.rs index ec53ea7a207..edef84851ec 100644 --- a/tokio/src/io/read_buf.rs +++ b/tokio/src/io/read_buf.rs @@ -39,9 +39,11 @@ impl<'a> ReadBuf<'a> { } } - /// Creates a new `ReadBuf` from a fully uninitialized buffer. + /// Creates a new `ReadBuf` from a buffer that may be uninitialized. /// - /// Use `assume_init` if part of the buffer is known to be already initialized. + /// The internal cursor will mark the entire buffer as uninitialized. If + /// the buffer is known to be partially initialized, then use `assume_init` + /// to move the internal cursor. #[inline] pub fn uninit(buf: &'a mut [MaybeUninit]) -> ReadBuf<'a> { ReadBuf { diff --git a/tokio/src/io/stderr.rs b/tokio/src/io/stderr.rs index e55cb1628fb..0988e2d9da0 100644 --- a/tokio/src/io/stderr.rs +++ b/tokio/src/io/stderr.rs @@ -67,8 +67,12 @@ cfg_io_std! { /// ``` pub fn stderr() -> Stderr { let std = io::stderr(); + // SAFETY: The `Read` implementation of `std` does not read from the + // buffer it is borrowing and correctly reports the length of the data + // written into the buffer. + let blocking = unsafe { Blocking::new(std) }; Stderr { - std: SplitByUtf8BoundaryIfWindows::new(Blocking::new(std)), + std: SplitByUtf8BoundaryIfWindows::new(blocking), } } } diff --git a/tokio/src/io/stdin.rs b/tokio/src/io/stdin.rs index 640cb0b7236..877c48b30fb 100644 --- a/tokio/src/io/stdin.rs +++ b/tokio/src/io/stdin.rs @@ -42,8 +42,12 @@ cfg_io_std! { /// user input and use blocking IO directly in that thread. pub fn stdin() -> Stdin { let std = io::stdin(); + // SAFETY: The `Read` implementation of `std` does not read from the + // buffer it is borrowing and correctly reports the length of the data + // written into the buffer. + let std = unsafe { Blocking::new(std) }; Stdin { - std: Blocking::new(std), + std, } } } diff --git a/tokio/src/io/stdout.rs b/tokio/src/io/stdout.rs index b4621469202..f46ca0f05c4 100644 --- a/tokio/src/io/stdout.rs +++ b/tokio/src/io/stdout.rs @@ -116,8 +116,12 @@ cfg_io_std! { /// ``` pub fn stdout() -> Stdout { let std = io::stdout(); + // SAFETY: The `Read` implementation of `std` does not read from the + // buffer it is borrowing and correctly reports the length of the data + // written into the buffer. + let blocking = unsafe { Blocking::new(std) }; Stdout { - std: SplitByUtf8BoundaryIfWindows::new(Blocking::new(std)), + std: SplitByUtf8BoundaryIfWindows::new(blocking), } } } diff --git a/tokio/src/io/util/async_read_ext.rs b/tokio/src/io/util/async_read_ext.rs index 4007e4993ee..8c80385128f 100644 --- a/tokio/src/io/util/async_read_ext.rs +++ b/tokio/src/io/util/async_read_ext.rs @@ -124,7 +124,7 @@ cfg_io_util! { /// /// No guarantees are provided about the contents of `buf` when this /// function is called, implementations cannot rely on any property of the - /// contents of `buf` being `true`. It is recommended that *implementations* + /// contents of `buf` being true. It is recommended that *implementations* /// only write data to `buf` instead of reading its contents. /// /// Correspondingly, however, *callers* of this method may not assume diff --git a/tokio/src/io/util/repeat.rs b/tokio/src/io/util/repeat.rs index ecdbc7d062c..33a8fd9862b 100644 --- a/tokio/src/io/util/repeat.rs +++ b/tokio/src/io/util/repeat.rs @@ -1,3 +1,5 @@ +use bytes::BufMut; + use crate::io::util::poll_proceed_and_make_progress; use crate::io::{AsyncRead, ReadBuf}; @@ -56,10 +58,7 @@ impl AsyncRead for Repeat { ) -> Poll> { ready!(crate::trace::trace_leaf(cx)); ready!(poll_proceed_and_make_progress(cx)); - // TODO: could be faster, but should we unsafe it? - while buf.remaining() != 0 { - buf.put_slice(&[self.byte]); - } + buf.put_bytes(self.byte, buf.remaining()); Poll::Ready(Ok(())) } } diff --git a/tokio/src/lib.rs b/tokio/src/lib.rs index 9bac62c4daa..a69def93634 100644 --- a/tokio/src/lib.rs +++ b/tokio/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![allow( clippy::cognitive_complexity, clippy::large_enum_variant, @@ -19,6 +18,7 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, allow(unused_attributes))] #![cfg_attr(loom, allow(dead_code, unreachable_pub))] +#![cfg_attr(windows, allow(rustdoc::broken_intra_doc_links))] //! A runtime for writing reliable network applications without compromising speed. //! @@ -283,7 +283,7 @@ //! loop { //! let n = match socket.read(&mut buf).await { //! // socket closed -//! Ok(n) if n == 0 => return, +//! Ok(0) => return, //! Ok(n) => n, //! Err(e) => { //! eprintln!("failed to read from socket; err = {:?}", e); @@ -633,15 +633,15 @@ pub mod stream {} // local re-exports of platform specific things, allowing for decent // documentation to be shimmed in on docs.rs -#[cfg(docsrs)] +#[cfg(all(docsrs, unix))] pub mod doc; #[cfg(any(feature = "net", feature = "fs"))] -#[cfg(docsrs)] +#[cfg(all(docsrs, unix))] #[allow(unused)] pub(crate) use self::doc::os; -#[cfg(not(docsrs))] +#[cfg(not(all(docsrs, unix)))] #[allow(unused)] pub(crate) use std::os; diff --git a/tokio/src/loom/std/atomic_u16.rs b/tokio/src/loom/std/atomic_u16.rs index 32ae349c33f..a9bdcb573c6 100644 --- a/tokio/src/loom/std/atomic_u16.rs +++ b/tokio/src/loom/std/atomic_u16.rs @@ -26,8 +26,7 @@ impl AtomicU16 { /// All mutations must have happened before the unsynchronized load. /// Additionally, there must be no concurrent mutations. pub(crate) unsafe fn unsync_load(&self) -> u16 { - // See - self.load(std::sync::atomic::Ordering::Relaxed) + core::ptr::read(self.inner.get() as *const u16) } } diff --git a/tokio/src/loom/std/atomic_u32.rs b/tokio/src/loom/std/atomic_u32.rs index c34ca940f8a..004bd9d75f7 100644 --- a/tokio/src/loom/std/atomic_u32.rs +++ b/tokio/src/loom/std/atomic_u32.rs @@ -26,8 +26,7 @@ impl AtomicU32 { /// All mutations must have happened before the unsynchronized load. /// Additionally, there must be no concurrent mutations. pub(crate) unsafe fn unsync_load(&self) -> u32 { - // See - self.load(std::sync::atomic::Ordering::Relaxed) + core::ptr::read(self.inner.get() as *const u32) } } diff --git a/tokio/src/loom/std/atomic_usize.rs b/tokio/src/loom/std/atomic_usize.rs index f88cc4bf727..0dee481b68e 100644 --- a/tokio/src/loom/std/atomic_usize.rs +++ b/tokio/src/loom/std/atomic_usize.rs @@ -26,8 +26,7 @@ impl AtomicUsize { /// All mutations must have happened before the unsynchronized load. /// Additionally, there must be no concurrent mutations. pub(crate) unsafe fn unsync_load(&self) -> usize { - // See - self.load(std::sync::atomic::Ordering::Relaxed) + core::ptr::read(self.inner.get() as *const usize) } pub(crate) fn with_mut(&mut self, f: impl FnOnce(&mut usize) -> R) -> R { diff --git a/tokio/src/loom/std/mod.rs b/tokio/src/loom/std/mod.rs index d446f2ee804..14e552a9aa5 100644 --- a/tokio/src/loom/std/mod.rs +++ b/tokio/src/loom/std/mod.rs @@ -95,22 +95,16 @@ pub(crate) mod sys { match std::env::var(ENV_WORKER_THREADS) { Ok(s) => { let n = s.parse().unwrap_or_else(|e| { - panic!( - "\"{}\" must be usize, error: {}, value: {}", - ENV_WORKER_THREADS, e, s - ) + panic!("\"{ENV_WORKER_THREADS}\" must be usize, error: {e}, value: {s}") }); - assert!(n > 0, "\"{}\" cannot be set to 0", ENV_WORKER_THREADS); + assert!(n > 0, "\"{ENV_WORKER_THREADS}\" cannot be set to 0"); n } Err(std::env::VarError::NotPresent) => { std::thread::available_parallelism().map_or(1, NonZeroUsize::get) } Err(std::env::VarError::NotUnicode(e)) => { - panic!( - "\"{}\" must be valid unicode, error: {:?}", - ENV_WORKER_THREADS, e - ) + panic!("\"{ENV_WORKER_THREADS}\" must be valid unicode, error: {e:?}") } } } diff --git a/tokio/src/loom/std/mutex.rs b/tokio/src/loom/std/mutex.rs index 3ea8e1df861..9593ec487d1 100644 --- a/tokio/src/loom/std/mutex.rs +++ b/tokio/src/loom/std/mutex.rs @@ -1,7 +1,7 @@ use std::sync::{self, MutexGuard, TryLockError}; /// Adapter for `std::Mutex` that removes the poisoning aspects -/// from its api. +/// from its API. #[derive(Debug)] pub(crate) struct Mutex(sync::Mutex); diff --git a/tokio/src/net/tcp/listener.rs b/tokio/src/net/tcp/listener.rs index 618da62b477..1545e654128 100644 --- a/tokio/src/net/tcp/listener.rs +++ b/tokio/src/net/tcp/listener.rs @@ -83,6 +83,7 @@ impl TcpListener { /// # Examples /// /// ```no_run + /// # if cfg!(miri) { return } // No `socket` in miri. /// use tokio::net::TcpListener; /// /// use std::io; diff --git a/tokio/src/net/tcp/socket.rs b/tokio/src/net/tcp/socket.rs index a9ba3b0660d..a9b454b3c52 100644 --- a/tokio/src/net/tcp/socket.rs +++ b/tokio/src/net/tcp/socket.rs @@ -469,6 +469,7 @@ impl TcpSocket { target_os = "redox", target_os = "solaris", target_os = "illumos", + target_os = "haiku" )))] #[cfg_attr( docsrs, @@ -477,6 +478,7 @@ impl TcpSocket { target_os = "redox", target_os = "solaris", target_os = "illumos", + target_os = "haiku" )))) )] pub fn tos(&self) -> io::Result { @@ -496,6 +498,7 @@ impl TcpSocket { target_os = "redox", target_os = "solaris", target_os = "illumos", + target_os = "haiku" )))] #[cfg_attr( docsrs, @@ -504,6 +507,7 @@ impl TcpSocket { target_os = "redox", target_os = "solaris", target_os = "illumos", + target_os = "haiku" )))) )] pub fn set_tos(&self, tos: u32) -> io::Result<()> { @@ -739,6 +743,7 @@ impl TcpSocket { /// # Examples /// /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// use tokio::net::TcpSocket; /// use socket2::{Domain, Socket, Type}; /// @@ -787,6 +792,9 @@ impl fmt::Debug for TcpSocket { } } +// These trait implementations can't be build on Windows, so we completely +// ignore them, even when building documentation. +#[cfg(unix)] cfg_unix! { impl AsRawFd for TcpSocket { fn as_raw_fd(&self) -> RawFd { diff --git a/tokio/src/net/tcp/stream.rs b/tokio/src/net/tcp/stream.rs index 82f4c05f67b..0504bf2e1ea 100644 --- a/tokio/src/net/tcp/stream.rs +++ b/tokio/src/net/tcp/stream.rs @@ -213,6 +213,7 @@ impl TcpStream { /// # Examples /// /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// use std::error::Error; /// use std::io::Read; /// use tokio::net::TcpListener; @@ -919,7 +920,7 @@ impl TcpStream { /// were written. /// /// Data is written from each buffer in order, with the final buffer read - /// from possible being only partially consumed. This method behaves + /// from possibly being only partially consumed. This method behaves /// equivalently to a single call to [`try_write()`] with concatenated /// buffers. /// diff --git a/tokio/src/net/udp.rs b/tokio/src/net/udp.rs index a34f7b9225b..2690a84b602 100644 --- a/tokio/src/net/udp.rs +++ b/tokio/src/net/udp.rs @@ -134,6 +134,7 @@ impl UdpSocket { /// # Example /// /// ```no_run + /// # if cfg!(miri) { return } // No `socket` in miri. /// use tokio::net::UdpSocket; /// use std::io; /// @@ -295,6 +296,7 @@ impl UdpSocket { /// # Example /// /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// use tokio::net::UdpSocket; /// /// # use std::{io, net::SocketAddr}; @@ -1501,6 +1503,148 @@ impl UdpSocket { .await } + /// Receives a single datagram from the connected address without removing it from the queue. + /// On success, returns the number of bytes read from whence the data came. + /// + /// # Notes + /// + /// On Windows, if the data is larger than the buffer specified, the buffer + /// is filled with the first part of the data, and `peek_from` returns the error + /// `WSAEMSGSIZE(10040)`. The excess data is lost. + /// Make sure to always use a sufficiently large buffer to hold the + /// maximum UDP packet size, which can be up to 65536 bytes in size. + /// + /// MacOS will return an error if you pass a zero-sized buffer. + /// + /// If you're merely interested in learning the sender of the data at the head of the queue, + /// try [`peek_sender`]. + /// + /// Note that the socket address **cannot** be implicitly trusted, because it is relatively + /// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack]. + /// Because UDP is stateless and does not validate the origin of a packet, + /// the attacker does not need to be able to intercept traffic in order to interfere. + /// It is important to be aware of this when designing your application-level protocol. + /// + /// # Examples + /// + /// ```no_run + /// use tokio::net::UdpSocket; + /// use std::io; + /// + /// #[tokio::main] + /// async fn main() -> io::Result<()> { + /// let socket = UdpSocket::bind("127.0.0.1:8080").await?; + /// + /// let mut buf = vec![0u8; 32]; + /// let len = socket.peek(&mut buf).await?; + /// + /// println!("peeked {:?} bytes", len); + /// + /// Ok(()) + /// } + /// ``` + /// + /// [`peek_sender`]: method@Self::peek_sender + /// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection + pub async fn peek(&self, buf: &mut [u8]) -> io::Result { + self.io + .registration() + .async_io(Interest::READABLE, || self.io.peek(buf)) + .await + } + + /// Receives data from the connected address, without removing it from the input queue. + /// On success, returns the sending address of the datagram. + /// + /// # Notes + /// + /// Note that on multiple calls to a `poll_*` method in the `recv` direction, only the + /// `Waker` from the `Context` passed to the most recent call will be scheduled to + /// receive a wakeup + /// + /// On Windows, if the data is larger than the buffer specified, the buffer + /// is filled with the first part of the data, and peek returns the error + /// `WSAEMSGSIZE(10040)`. The excess data is lost. + /// Make sure to always use a sufficiently large buffer to hold the + /// maximum UDP packet size, which can be up to 65536 bytes in size. + /// + /// MacOS will return an error if you pass a zero-sized buffer. + /// + /// If you're merely interested in learning the sender of the data at the head of the queue, + /// try [`poll_peek_sender`]. + /// + /// Note that the socket address **cannot** be implicitly trusted, because it is relatively + /// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack]. + /// Because UDP is stateless and does not validate the origin of a packet, + /// the attacker does not need to be able to intercept traffic in order to interfere. + /// It is important to be aware of this when designing your application-level protocol. + /// + /// # Return value + /// + /// The function returns: + /// + /// * `Poll::Pending` if the socket is not ready to read + /// * `Poll::Ready(Ok(()))` reads data into `ReadBuf` if the socket is ready + /// * `Poll::Ready(Err(e))` if an error is encountered. + /// + /// # Errors + /// + /// This function may encounter any standard I/O error except `WouldBlock`. + /// + /// [`poll_peek_sender`]: method@Self::poll_peek_sender + /// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection + pub fn poll_peek(&self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { + #[allow(clippy::blocks_in_conditions)] + let n = ready!(self.io.registration().poll_read_io(cx, || { + // Safety: will not read the maybe uninitialized bytes. + let b = unsafe { + &mut *(buf.unfilled_mut() as *mut [std::mem::MaybeUninit] as *mut [u8]) + }; + + self.io.peek(b) + }))?; + + // Safety: We trust `recv` to have filled up `n` bytes in the buffer. + unsafe { + buf.assume_init(n); + } + buf.advance(n); + Poll::Ready(Ok(())) + } + + /// Tries to receive data on the connected address without removing it from the input queue. + /// On success, returns the number of bytes read. + /// + /// When there is no pending data, `Err(io::ErrorKind::WouldBlock)` is + /// returned. This function is usually paired with `readable()`. + /// + /// # Notes + /// + /// On Windows, if the data is larger than the buffer specified, the buffer + /// is filled with the first part of the data, and peek returns the error + /// `WSAEMSGSIZE(10040)`. The excess data is lost. + /// Make sure to always use a sufficiently large buffer to hold the + /// maximum UDP packet size, which can be up to 65536 bytes in size. + /// + /// MacOS will return an error if you pass a zero-sized buffer. + /// + /// If you're merely interested in learning the sender of the data at the head of the queue, + /// try [`try_peek_sender`]. + /// + /// Note that the socket address **cannot** be implicitly trusted, because it is relatively + /// trivial to send a UDP datagram with a spoofed origin in a [packet injection attack]. + /// Because UDP is stateless and does not validate the origin of a packet, + /// the attacker does not need to be able to intercept traffic in order to interfere. + /// It is important to be aware of this when designing your application-level protocol. + /// + /// [`try_peek_sender`]: method@Self::try_peek_sender + /// [packet injection attack]: https://en.wikipedia.org/wiki/Packet_injection + pub fn try_peek(&self, buf: &mut [u8]) -> io::Result { + self.io + .registration() + .try_io(Interest::READABLE, || self.io.peek(buf)) + } + /// Receives data from the socket, without removing it from the input queue. /// On success, returns the number of bytes read and the address from whence /// the data came. @@ -1864,6 +2008,7 @@ impl UdpSocket { target_os = "redox", target_os = "solaris", target_os = "illumos", + target_os = "haiku" )))] #[cfg_attr( docsrs, @@ -1872,6 +2017,7 @@ impl UdpSocket { target_os = "redox", target_os = "solaris", target_os = "illumos", + target_os = "haiku" )))) )] pub fn tos(&self) -> io::Result { @@ -1891,6 +2037,7 @@ impl UdpSocket { target_os = "redox", target_os = "solaris", target_os = "illumos", + target_os = "haiku" )))] #[cfg_attr( docsrs, @@ -1899,6 +2046,7 @@ impl UdpSocket { target_os = "redox", target_os = "solaris", target_os = "illumos", + target_os = "haiku" )))) )] pub fn set_tos(&self, tos: u32) -> io::Result<()> { @@ -1975,6 +2123,7 @@ impl UdpSocket { /// /// # Examples /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// use tokio::net::UdpSocket; /// use std::io; /// diff --git a/tokio/src/net/unix/datagram/socket.rs b/tokio/src/net/unix/datagram/socket.rs index 0fde57da133..e475778ef66 100644 --- a/tokio/src/net/unix/datagram/socket.rs +++ b/tokio/src/net/unix/datagram/socket.rs @@ -35,6 +35,7 @@ cfg_net_unix! { /// # Examples /// Using named sockets, associated with a filesystem path: /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -67,6 +68,7 @@ cfg_net_unix! { /// /// Using unnamed sockets, created as a pair /// ``` + /// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -371,6 +373,7 @@ impl UnixDatagram { /// /// # Examples /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -403,6 +406,7 @@ impl UnixDatagram { /// /// # Examples /// ``` + /// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -457,6 +461,7 @@ impl UnixDatagram { /// explicitly with [`Runtime::enter`](crate::runtime::Runtime::enter) function. /// # Examples /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -521,6 +526,7 @@ impl UnixDatagram { /// /// # Examples /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -560,6 +566,7 @@ impl UnixDatagram { /// /// # Examples /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -604,6 +611,7 @@ impl UnixDatagram { /// /// # Examples /// ``` + /// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -734,6 +742,7 @@ impl UnixDatagram { /// /// # Examples /// ``` + /// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -884,6 +893,7 @@ impl UnixDatagram { /// /// # Examples /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -1000,6 +1010,7 @@ impl UnixDatagram { /// /// # Examples /// ``` + /// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -1050,6 +1061,7 @@ impl UnixDatagram { /// /// # Examples /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -1100,6 +1112,7 @@ impl UnixDatagram { /// /// # Examples /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -1416,6 +1429,7 @@ impl UnixDatagram { /// # Examples /// For a socket bound to a local path /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -1438,6 +1452,7 @@ impl UnixDatagram { /// /// For an unbound socket /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -1462,6 +1477,7 @@ impl UnixDatagram { /// # Examples /// For a peer with a local path /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -1487,6 +1503,7 @@ impl UnixDatagram { /// /// For an unbound peer /// ``` + /// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -1508,6 +1525,7 @@ impl UnixDatagram { /// /// # Examples /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { @@ -1535,6 +1553,7 @@ impl UnixDatagram { /// /// # Examples /// ``` + /// # if cfg!(miri) { return } // No SOCK_DGRAM for `socketpair` in miri. /// # use std::error::Error; /// # #[tokio::main] /// # async fn main() -> Result<(), Box> { diff --git a/tokio/src/net/unix/mod.rs b/tokio/src/net/unix/mod.rs index a94fc7b2711..f23e49f61ee 100644 --- a/tokio/src/net/unix/mod.rs +++ b/tokio/src/net/unix/mod.rs @@ -26,14 +26,14 @@ pub use ucred::UCred; pub mod pipe; -/// A type representing process and process group IDs. +/// A type representing user ID. #[allow(non_camel_case_types)] pub type uid_t = u32; -/// A type representing user ID. +/// A type representing group ID. #[allow(non_camel_case_types)] pub type gid_t = u32; -/// A type representing group ID. +/// A type representing process and process group IDs. #[allow(non_camel_case_types)] pub type pid_t = i32; diff --git a/tokio/src/net/unix/stream.rs b/tokio/src/net/unix/stream.rs index 4c93c646439..16dc03f9a30 100644 --- a/tokio/src/net/unix/stream.rs +++ b/tokio/src/net/unix/stream.rs @@ -142,7 +142,7 @@ impl UnixStream { /// // if the readiness event is a false positive. /// match stream.try_read(&mut data) { /// Ok(n) => { - /// println!("read {} bytes", n); + /// println!("read {} bytes", n); /// } /// Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { /// continue; @@ -833,6 +833,7 @@ impl UnixStream { /// # Examples /// /// ``` + /// # if cfg!(miri) { return } // No `socket` in miri. /// use std::error::Error; /// use std::io::Read; /// use tokio::net::UnixListener; diff --git a/tokio/src/net/unix/ucred.rs b/tokio/src/net/unix/ucred.rs index bcd1c755f6a..74e596bc9b6 100644 --- a/tokio/src/net/unix/ucred.rs +++ b/tokio/src/net/unix/ucred.rs @@ -35,7 +35,8 @@ impl UCred { target_os = "linux", target_os = "redox", target_os = "android", - target_os = "openbsd" + target_os = "openbsd", + target_os = "haiku" ))] pub(crate) use self::impl_linux::get_peer_cred; @@ -67,7 +68,8 @@ pub(crate) use self::impl_noproc::get_peer_cred; target_os = "linux", target_os = "redox", target_os = "android", - target_os = "openbsd" + target_os = "openbsd", + target_os = "haiku" ))] pub(crate) mod impl_linux { use crate::net::unix::{self, UnixStream}; @@ -77,7 +79,12 @@ pub(crate) mod impl_linux { #[cfg(target_os = "openbsd")] use libc::sockpeercred as ucred; - #[cfg(any(target_os = "linux", target_os = "redox", target_os = "android"))] + #[cfg(any( + target_os = "linux", + target_os = "redox", + target_os = "android", + target_os = "haiku" + ))] use libc::ucred; pub(crate) fn get_peer_cred(sock: &UnixStream) -> io::Result { diff --git a/tokio/src/net/windows/named_pipe.rs b/tokio/src/net/windows/named_pipe.rs index 0b312f896f1..417e67b31dd 100644 --- a/tokio/src/net/windows/named_pipe.rs +++ b/tokio/src/net/windows/named_pipe.rs @@ -17,7 +17,7 @@ cfg_io_util! { } // Hide imports which are not used when generating documentation. -#[cfg(not(docsrs))] +#[cfg(windows)] mod doc { pub(super) use crate::os::windows::ffi::OsStrExt; pub(super) mod windows_sys { @@ -30,7 +30,7 @@ mod doc { } // NB: none of these shows up in public API, so don't document them. -#[cfg(docsrs)] +#[cfg(not(windows))] mod doc { pub(super) mod mio_windows { pub type NamedPipe = crate::doc::NotDefinedHere; diff --git a/tokio/src/process/mod.rs b/tokio/src/process/mod.rs index bd4a7ecee7b..565795ac4e6 100644 --- a/tokio/src/process/mod.rs +++ b/tokio/src/process/mod.rs @@ -330,6 +330,15 @@ impl Command { &mut self.std } + /// Cheaply convert into a `std::process::Command`. + /// + /// Note that Tokio specific options will be lost. Currently, this only applies to [`kill_on_drop`]. + /// + /// [`kill_on_drop`]: Command::kill_on_drop + pub fn into_std(self) -> StdCommand { + self.std + } + /// Adds an argument to pass to the program. /// /// Only one argument can be passed per use. So instead of: @@ -801,6 +810,7 @@ impl Command { /// Basic usage: /// /// ```no_run + /// # if cfg!(miri) { return } // No `pidfd_spawnp` in miri. /// use tokio::process::Command; /// /// async fn run_ls() -> std::process::ExitStatus { @@ -971,6 +981,26 @@ impl Command { async { child?.wait_with_output().await } } + + /// Returns the boolean value that was previously set by [`Command::kill_on_drop`]. + /// + /// Note that if you have not previously called [`Command::kill_on_drop`], the + /// default value of `false` will be returned here. + /// + /// # Examples + /// + /// ``` + /// use tokio::process::Command; + /// + /// let mut cmd = Command::new("echo"); + /// assert!(!cmd.get_kill_on_drop()); + /// + /// cmd.kill_on_drop(true); + /// assert!(cmd.get_kill_on_drop()); + /// ``` + pub fn get_kill_on_drop(&self) -> bool { + self.kill_on_drop + } } impl From for Command { @@ -1183,6 +1213,7 @@ impl Child { /// This function is cancel safe. /// /// ``` + /// # if cfg!(miri) { return } // No `pidfd_spawnp` in miri. /// # #[cfg(not(unix))]fn main(){} /// # #[cfg(unix)] /// use tokio::io::AsyncWriteExt; diff --git a/tokio/src/process/windows.rs b/tokio/src/process/windows.rs index 792a9c9610b..db3c15790ce 100644 --- a/tokio/src/process/windows.rs +++ b/tokio/src/process/windows.rs @@ -242,7 +242,11 @@ where use std::os::windows::prelude::FromRawHandle; let raw = Arc::new(unsafe { StdFile::from_raw_handle(io.into_raw_handle()) }); - let io = Blocking::new(ArcFile(raw.clone())); + let io = ArcFile(raw.clone()); + // SAFETY: the `Read` implementation of `io` does not + // read from the buffer it is borrowing and correctly + // reports the length of the data written into the buffer. + let io = unsafe { Blocking::new(io) }; Ok(ChildStdio { raw, io }) } diff --git a/tokio/src/runtime/blocking/pool.rs b/tokio/src/runtime/blocking/pool.rs index 7eec91d23d9..23180dc5245 100644 --- a/tokio/src/runtime/blocking/pool.rs +++ b/tokio/src/runtime/blocking/pool.rs @@ -128,7 +128,7 @@ pub(crate) struct Task { #[derive(PartialEq, Eq)] pub(crate) enum Mandatory { - #[cfg_attr(not(fs), allow(dead_code))] + #[cfg_attr(not(feature = "fs"), allow(dead_code))] Mandatory, NonMandatory, } @@ -322,7 +322,7 @@ impl Spawner { // Compat: do not panic here, return the join_handle even though it will never resolve Err(SpawnError::ShuttingDown) => join_handle, Err(SpawnError::NoThreads(e)) => { - panic!("OS can't spawn worker thread: {}", e) + panic!("OS can't spawn worker thread: {e}") } } } diff --git a/tokio/src/runtime/builder.rs b/tokio/src/runtime/builder.rs index ac13db68c4c..c9a47c3862c 100644 --- a/tokio/src/runtime/builder.rs +++ b/tokio/src/runtime/builder.rs @@ -706,11 +706,11 @@ impl Builder { /// /// This *does not* support [`LocalSet`](crate::task::LocalSet) at this time. /// - /// **Note**: This is an [unstable API][unstable]. The public API of this type - /// may break in 1.x releases. See [the documentation on unstable - /// features][unstable] for details. - /// - /// [unstable]: crate#unstable-features + /// **Note**: This is an [unstable API][unstable]. The public API of this type + /// may break in 1.x releases. See [the documentation on unstable + /// features][unstable] for details. + /// + /// [unstable]: crate#unstable-features /// /// # Examples /// @@ -755,11 +755,11 @@ impl Builder { /// /// This *does not* support [`LocalSet`](crate::task::LocalSet) at this time. /// - /// **Note**: This is an [unstable API][unstable]. The public API of this type - /// may break in 1.x releases. See [the documentation on unstable - /// features][unstable] for details. - /// - /// [unstable]: crate#unstable-features + /// **Note**: This is an [unstable API][unstable]. The public API of this type + /// may break in 1.x releases. See [the documentation on unstable + /// features][unstable] for details. + /// + /// [unstable]: crate#unstable-features /// /// # Examples /// @@ -1245,8 +1245,31 @@ impl Builder { /// .unwrap(); /// ``` /// + /// When migrating from the legacy histogram ([`HistogramScale::Log`]) and wanting + /// to match the previous behavior, use `precision_exact(0)`. This creates a histogram + /// where each bucket is twice the size of the previous bucket. + /// ```rust + /// use std::time::Duration; + /// use tokio::runtime::{HistogramConfiguration, LogHistogram}; + /// let rt = tokio::runtime::Builder::new_current_thread() + /// .enable_all() + /// .enable_metrics_poll_time_histogram() + /// .metrics_poll_time_histogram_configuration(HistogramConfiguration::log( + /// LogHistogram::builder() + /// .min_value(Duration::from_micros(20)) + /// .max_value(Duration::from_millis(4)) + /// // Set `precision_exact` to `0` to match `HistogramScale::Log` + /// .precision_exact(0) + /// .max_buckets(10) + /// .unwrap(), + /// )) + /// .build() + /// .unwrap(); + /// ``` + /// /// [`LogHistogram`]: crate::runtime::LogHistogram /// [default configuration]: crate::runtime::LogHistogramBuilder + /// [`HistogramScale::Log`]: crate::runtime::HistogramScale::Log pub fn metrics_poll_time_histogram_configuration(&mut self, configuration: HistogramConfiguration) -> &mut Self { self.metrics_poll_count_histogram.histogram_type = configuration.inner; self diff --git a/tokio/src/runtime/context.rs b/tokio/src/runtime/context.rs index 76918114bc3..12f6edf1321 100644 --- a/tokio/src/runtime/context.rs +++ b/tokio/src/runtime/context.rs @@ -183,7 +183,14 @@ cfg_rt! { #[track_caller] pub(super) fn with_scheduler(f: impl FnOnce(Option<&scheduler::Context>) -> R) -> R { let mut f = Some(f); - CONTEXT.try_with(|c| c.scheduler.with(f.take().unwrap())) + CONTEXT.try_with(|c| { + let f = f.take().unwrap(); + if matches!(c.runtime.get(), EnterRuntime::Entered { .. }) { + c.scheduler.with(f) + } else { + f(None) + } + }) .unwrap_or_else(|_| (f.take().unwrap())(None)) } diff --git a/tokio/src/runtime/dump.rs b/tokio/src/runtime/dump.rs index aea2381127b..0a6adf979b7 100644 --- a/tokio/src/runtime/dump.rs +++ b/tokio/src/runtime/dump.rs @@ -1,13 +1,15 @@ //! Snapshots of runtime state. //! -//! See [Handle::dump][crate::runtime::Handle::dump]. +//! See [`Handle::dump`][crate::runtime::Handle::dump]. use crate::task::Id; -use std::fmt; +use std::{fmt, future::Future, path::Path}; + +pub use crate::runtime::task::trace::Root; /// A snapshot of a runtime's state. /// -/// See [Handle::dump][crate::runtime::Handle::dump]. +/// See [`Handle::dump`][crate::runtime::Handle::dump]. #[derive(Debug)] pub struct Dump { tasks: Tasks, @@ -15,7 +17,7 @@ pub struct Dump { /// Snapshots of tasks. /// -/// See [Handle::dump][crate::runtime::Handle::dump]. +/// See [`Handle::dump`][crate::runtime::Handle::dump]. #[derive(Debug)] pub struct Tasks { tasks: Vec, @@ -23,21 +25,265 @@ pub struct Tasks { /// A snapshot of a task. /// -/// See [Handle::dump][crate::runtime::Handle::dump]. +/// See [`Handle::dump`][crate::runtime::Handle::dump]. #[derive(Debug)] pub struct Task { id: Id, trace: Trace, } +/// Represents an address that should not be dereferenced. +/// +/// This type exists to get the auto traits correct, the public API +/// uses raw pointers to make life easier for users. +#[derive(Copy, Clone, Debug)] +struct Address(*mut std::ffi::c_void); + +// Safe since Address should not be dereferenced +unsafe impl Send for Address {} +unsafe impl Sync for Address {} + +/// A backtrace symbol. +/// +/// This struct provides accessors for backtrace symbols, similar to [`backtrace::BacktraceSymbol`]. +#[derive(Clone, Debug)] +pub struct BacktraceSymbol { + name: Option>, + name_demangled: Option>, + addr: Option
, + filename: Option, + lineno: Option, + colno: Option, +} + +impl BacktraceSymbol { + pub(crate) fn from_backtrace_symbol(sym: &backtrace::BacktraceSymbol) -> Self { + let name = sym.name(); + Self { + name: name.as_ref().map(|name| name.as_bytes().into()), + name_demangled: name.map(|name| format!("{}", name).into()), + addr: sym.addr().map(Address), + filename: sym.filename().map(From::from), + lineno: sym.lineno(), + colno: sym.colno(), + } + } + + /// Return the raw name of the symbol. + pub fn name_raw(&self) -> Option<&[u8]> { + self.name.as_deref() + } + + /// Return the demangled name of the symbol. + pub fn name_demangled(&self) -> Option<&str> { + self.name_demangled.as_deref() + } + + /// Returns the starting address of this symbol. + pub fn addr(&self) -> Option<*mut std::ffi::c_void> { + self.addr.map(|addr| addr.0) + } + + /// Returns the file name where this function was defined. If debuginfo + /// is missing, this is likely to return None. + pub fn filename(&self) -> Option<&Path> { + self.filename.as_deref() + } + + /// Returns the line number for where this symbol is currently executing. + /// + /// If debuginfo is missing, this is likely to return `None`. + pub fn lineno(&self) -> Option { + self.lineno + } + + /// Returns the column number for where this symbol is currently executing. + /// + /// If debuginfo is missing, this is likely to return `None`. + pub fn colno(&self) -> Option { + self.colno + } +} + +/// A backtrace frame. +/// +/// This struct represents one stack frame in a captured backtrace, similar to [`backtrace::BacktraceFrame`]. +#[derive(Clone, Debug)] +pub struct BacktraceFrame { + ip: Address, + symbol_address: Address, + symbols: Box<[BacktraceSymbol]>, +} + +impl BacktraceFrame { + pub(crate) fn from_resolved_backtrace_frame(frame: &backtrace::BacktraceFrame) -> Self { + Self { + ip: Address(frame.ip()), + symbol_address: Address(frame.symbol_address()), + symbols: frame + .symbols() + .iter() + .map(BacktraceSymbol::from_backtrace_symbol) + .collect(), + } + } + + /// Return the instruction pointer of this frame. + /// + /// See the ABI docs for your platform for the exact meaning. + pub fn ip(&self) -> *mut std::ffi::c_void { + self.ip.0 + } + + /// Returns the starting symbol address of the frame of this function. + pub fn symbol_address(&self) -> *mut std::ffi::c_void { + self.symbol_address.0 + } + + /// Return an iterator over the symbols of this backtrace frame. + /// + /// Due to inlining, it is possible for there to be multiple [`BacktraceSymbol`] items relating + /// to a single frame. The first symbol listed is the "innermost function", + /// whereas the last symbol is the outermost (last caller). + pub fn symbols(&self) -> impl Iterator { + self.symbols.iter() + } +} + +/// A captured backtrace. +/// +/// This struct provides access to each backtrace frame, similar to [`backtrace::Backtrace`]. +#[derive(Clone, Debug)] +pub struct Backtrace { + frames: Box<[BacktraceFrame]>, +} + +impl Backtrace { + /// Return the frames in this backtrace, innermost (in a task dump, + /// likely to be a leaf future's poll function) first. + pub fn frames(&self) -> impl Iterator { + self.frames.iter() + } +} + /// An execution trace of a task's last poll. /// -/// See [Handle::dump][crate::runtime::Handle::dump]. +///
+/// +/// Resolving a backtrace, either via the [`Display`][std::fmt::Display] impl or via +/// [`resolve_backtraces`][Trace::resolve_backtraces], parses debuginfo, which is +/// possibly a CPU-expensive operation that can take a platform-specific but +/// long time to run - often over 100 milliseconds, especially if the current +/// process's binary is big. In some cases, the platform might internally cache some of the +/// debuginfo, so successive calls to `resolve_backtraces` might be faster than +/// the first call, but all guarantees are platform-dependent. +/// +/// To avoid blocking the runtime, it is recommended +/// that you resolve backtraces inside of a [`spawn_blocking()`][crate::task::spawn_blocking] +/// and to have some concurrency-limiting mechanism to avoid unexpected performance impact. +///
+/// +/// See [`Handle::dump`][crate::runtime::Handle::dump]. #[derive(Debug)] pub struct Trace { inner: super::task::trace::Trace, } +impl Trace { + /// Resolve and return a list of backtraces that are involved in polls in this trace. + /// + /// The exact backtraces included here are unstable and might change in the future, + /// but you can expect one [`Backtrace`] for every call to + /// [`poll`] to a bottom-level Tokio future - so if something like [`join!`] is + /// used, there will be a backtrace for each future in the join. + /// + /// [`poll`]: std::future::Future::poll + /// [`join!`]: macro@join + pub fn resolve_backtraces(&self) -> Vec { + self.inner + .backtraces() + .iter() + .map(|backtrace| { + let mut backtrace = backtrace::Backtrace::from(backtrace.clone()); + backtrace.resolve(); + Backtrace { + frames: backtrace + .frames() + .iter() + .map(BacktraceFrame::from_resolved_backtrace_frame) + .collect(), + } + }) + .collect() + } + + /// Runs the function `f` in tracing mode, and returns its result along with the resulting [`Trace`]. + /// + /// This is normally called with `f` being the poll function of a future, and will give you a backtrace + /// that tells you what that one future is doing. + /// + /// Use [`Handle::dump`] instead if you want to know what *all the tasks* in your program are doing. + /// Also see [`Handle::dump`] for more documentation about dumps, but unlike [`Handle::dump`], this function + /// should not be much slower than calling `f` directly. + /// + /// Due to the way tracing is implemented, Tokio leaf futures will usually, instead of doing their + /// actual work, do the equivalent of a `yield_now` (returning a `Poll::Pending` and scheduling the + /// current context for execution), which means forward progress will probably not happen unless + /// you eventually call your future outside of `capture`. + /// + /// [`Handle::dump`]: crate::runtime::Handle::dump + /// + /// Example usage: + /// ``` + /// use std::future::Future; + /// use std::task::Poll; + /// use tokio::runtime::dump::Trace; + /// + /// # async fn test_fn() { + /// // some future + /// let mut test_future = std::pin::pin!(async move { tokio::task::yield_now().await; 0 }); + /// + /// // trace it once, see what it's doing + /// let (trace, res) = Trace::root(std::future::poll_fn(|cx| { + /// let (res, trace) = Trace::capture(|| test_future.as_mut().poll(cx)); + /// Poll::Ready((trace, res)) + /// })).await; + /// + /// // await it to let it finish, outside of a `capture` + /// let output = match res { + /// Poll::Ready(output) => output, + /// Poll::Pending => test_future.await, + /// }; + /// + /// println!("{trace}"); + /// # } + /// ``` + /// + /// ### Nested calls + /// + /// Nested calls to `capture` might return partial traces, but will not do any other undesirable behavior (for + /// example, they will not panic). + pub fn capture(f: F) -> (R, Trace) + where + F: FnOnce() -> R, + { + let (res, trace) = super::task::trace::Trace::capture(f); + (res, Trace { inner: trace }) + } + + /// Create a root for stack traces captured using [`Trace::capture`]. Stack frames above + /// the root will not be captured. + /// + /// Nesting multiple [`Root`] futures is fine. Captures will stop at the first root. Not having + /// a [`Root`] is fine as well, but there is no guarantee on where the capture will stop. + pub fn root(f: F) -> Root + where + F: Future, + { + crate::runtime::task::trace::Trace::root(f) + } +} + impl Dump { pub(crate) fn new(tasks: Vec) -> Self { Self { diff --git a/tokio/src/runtime/handle.rs b/tokio/src/runtime/handle.rs index 752640d75bd..91f13d6c2ed 100644 --- a/tokio/src/runtime/handle.rs +++ b/tokio/src/runtime/handle.rs @@ -447,6 +447,9 @@ cfg_taskdump! { impl Handle { /// Captures a snapshot of the runtime's state. /// + /// If you only want to capture a snapshot of a single future's state, you can use + /// [`Trace::capture`][crate::runtime::dump::Trace]. + /// /// This functionality is experimental, and comes with a number of /// requirements and limitations. /// diff --git a/tokio/src/runtime/io/driver.rs b/tokio/src/runtime/io/driver.rs index 5b97a8802de..1139cbf580c 100644 --- a/tokio/src/runtime/io/driver.rs +++ b/tokio/src/runtime/io/driver.rs @@ -154,7 +154,7 @@ impl Driver { // In case of wasm32_wasi this error happens, when trying to poll without subscriptions // just return from the park, as there would be nothing, which wakes us up. } - Err(e) => panic!("unexpected error when polling the I/O driver: {:?}", e), + Err(e) => panic!("unexpected error when polling the I/O driver: {e:?}"), } // Process all the events that came in, dispatching appropriately diff --git a/tokio/src/runtime/io/registration_set.rs b/tokio/src/runtime/io/registration_set.rs index 9b2f3f13c43..21f6c873fb1 100644 --- a/tokio/src/runtime/io/registration_set.rs +++ b/tokio/src/runtime/io/registration_set.rs @@ -7,6 +7,9 @@ use std::ptr::NonNull; use std::sync::atomic::Ordering::{Acquire, Release}; use std::sync::Arc; +// Kind of arbitrary, but buffering 16 `ScheduledIo`s doesn't seem like much +const NOTIFY_AFTER: usize = 16; + pub(super) struct RegistrationSet { num_pending_release: AtomicUsize, } @@ -35,7 +38,7 @@ impl RegistrationSet { let synced = Synced { is_shutdown: false, registrations: LinkedList::new(), - pending_release: Vec::with_capacity(16), + pending_release: Vec::with_capacity(NOTIFY_AFTER), }; (set, synced) @@ -69,9 +72,6 @@ impl RegistrationSet { // Returns `true` if the caller should unblock the I/O driver to purge // registrations pending release. pub(super) fn deregister(&self, synced: &mut Synced, registration: &Arc) -> bool { - // Kind of arbitrary, but buffering 16 `ScheduledIo`s doesn't seem like much - const NOTIFY_AFTER: usize = 16; - synced.pending_release.push(registration.clone()); let len = synced.pending_release.len(); @@ -106,7 +106,7 @@ impl RegistrationSet { for io in pending { // safety: the registration is part of our list - unsafe { self.remove(synced, io.as_ref()) } + unsafe { self.remove(synced, &io) } } self.num_pending_release.store(0, Release); @@ -114,9 +114,12 @@ impl RegistrationSet { // This function is marked as unsafe, because the caller must make sure that // `io` is part of the registration set. - pub(super) unsafe fn remove(&self, synced: &mut Synced, io: &ScheduledIo) { - super::EXPOSE_IO.unexpose_provenance(io); - let _ = synced.registrations.remove(io.into()); + pub(super) unsafe fn remove(&self, synced: &mut Synced, io: &Arc) { + // SAFETY: Pointers into an Arc are never null. + let io = unsafe { NonNull::new_unchecked(Arc::as_ptr(io).cast_mut()) }; + + super::EXPOSE_IO.unexpose_provenance(io.as_ptr()); + let _ = synced.registrations.remove(io); } } diff --git a/tokio/src/runtime/io/scheduled_io.rs b/tokio/src/runtime/io/scheduled_io.rs index ee6977c00e7..af57caed460 100644 --- a/tokio/src/runtime/io/scheduled_io.rs +++ b/tokio/src/runtime/io/scheduled_io.rs @@ -206,43 +206,23 @@ impl ScheduledIo { /// specific tick. /// - `f`: a closure returning a new readiness value given the previous /// readiness. - pub(super) fn set_readiness(&self, tick: Tick, f: impl Fn(Ready) -> Ready) { - let mut current = self.readiness.load(Acquire); - - // If the io driver is shut down, then you are only allowed to clear readiness. - debug_assert!(SHUTDOWN.unpack(current) == 0 || matches!(tick, Tick::Clear(_))); - - loop { - // Mask out the tick bits so that the modifying function doesn't see - // them. - let current_readiness = Ready::from_usize(current); - let new = f(current_readiness); - - let new_tick = match tick { - Tick::Set => { - let current = TICK.unpack(current); - current.wrapping_add(1) % (TICK.max_value() + 1) - } - Tick::Clear(t) => { - if TICK.unpack(current) as u8 != t { - // Trying to clear readiness with an old event! - return; - } - - t as usize - } + pub(super) fn set_readiness(&self, tick_op: Tick, f: impl Fn(Ready) -> Ready) { + let _ = self.readiness.fetch_update(AcqRel, Acquire, |curr| { + // If the io driver is shut down, then you are only allowed to clear readiness. + debug_assert!(SHUTDOWN.unpack(curr) == 0 || matches!(tick_op, Tick::Clear(_))); + + const MAX_TICK: usize = TICK.max_value() + 1; + let tick = TICK.unpack(curr); + + let new_tick = match tick_op { + // Trying to clear readiness with an old event! + Tick::Clear(t) if tick as u8 != t => return None, + Tick::Clear(t) => t as usize, + Tick::Set => tick.wrapping_add(1) % MAX_TICK, }; - let next = TICK.pack(new_tick, new.as_usize()); - - match self - .readiness - .compare_exchange(current, next, AcqRel, Acquire) - { - Ok(_) => return, - // we lost the race, retry! - Err(actual) => current = actual, - } - } + let ready = Ready::from_usize(READINESS.unpack(curr)); + Some(TICK.pack(new_tick, f(ready).as_usize())) + }); } /// Notifies all pending waiters that have registered interest in `ready`. @@ -335,22 +315,16 @@ impl ScheduledIo { if ready.is_empty() && !is_shutdown { // Update the task info let mut waiters = self.waiters.lock(); - let slot = match direction { + let waker = match direction { Direction::Read => &mut waiters.reader, Direction::Write => &mut waiters.writer, }; // Avoid cloning the waker if one is already stored that matches the // current task. - match slot { - Some(existing) => { - if !existing.will_wake(cx.waker()) { - existing.clone_from(cx.waker()); - } - } - None => { - *slot = Some(cx.waker().clone()); - } + match waker { + Some(waker) => waker.clone_from(cx.waker()), + None => *waker = Some(cx.waker().clone()), } // Try again, in case the readiness was changed while we were @@ -465,12 +439,11 @@ impl Future for Readiness<'_> { State::Init => { // Optimistically check existing readiness let curr = scheduled_io.readiness.load(SeqCst); - let ready = Ready::from_usize(READINESS.unpack(curr)); let is_shutdown = SHUTDOWN.unpack(curr) != 0; // Safety: `waiter.interest` never changes let interest = unsafe { (*waiter.get()).interest }; - let ready = ready.intersection(interest); + let ready = Ready::from_usize(READINESS.unpack(curr)).intersection(interest); if !ready.is_empty() || is_shutdown { // Currently ready! @@ -538,10 +511,7 @@ impl Future for Readiness<'_> { *state = State::Done; } else { // Update the waker, if necessary. - if !w.waker.as_ref().unwrap().will_wake(cx.waker()) { - w.waker = Some(cx.waker().clone()); - } - + w.waker.as_mut().unwrap().clone_from(cx.waker()); return Poll::Pending; } @@ -566,8 +536,7 @@ impl Future for Readiness<'_> { // The readiness state could have been cleared in the meantime, // but we allow the returned ready set to be empty. - let curr_ready = Ready::from_usize(READINESS.unpack(curr)); - let ready = curr_ready.intersection(w.interest); + let ready = Ready::from_usize(READINESS.unpack(curr)).intersection(w.interest); return Poll::Ready(ReadyEvent { tick, diff --git a/tokio/src/runtime/local_runtime/runtime.rs b/tokio/src/runtime/local_runtime/runtime.rs index 0f2b944e4eb..358a771956b 100644 --- a/tokio/src/runtime/local_runtime/runtime.rs +++ b/tokio/src/runtime/local_runtime/runtime.rs @@ -166,7 +166,8 @@ impl LocalRuntime { /// /// This function _will_ be run on another thread. /// - /// See the documentation in the non-local runtime for more information. + /// See the [documentation in the non-local runtime][Runtime] for more + /// information. /// /// [Runtime]: crate::runtime::Runtime::spawn_blocking /// @@ -197,7 +198,8 @@ impl LocalRuntime { /// Runs a future to completion on the Tokio runtime. This is the /// runtime's entry point. /// - /// See the documentation for [the equivalent method on Runtime] for more information. + /// See the documentation for [the equivalent method on Runtime][Runtime] + /// for more information. /// /// [Runtime]: crate::runtime::Runtime::block_on /// diff --git a/tokio/src/runtime/metrics/histogram/h2_histogram.rs b/tokio/src/runtime/metrics/histogram/h2_histogram.rs index 09e554440f4..fa0f2dc5f34 100644 --- a/tokio/src/runtime/metrics/histogram/h2_histogram.rs +++ b/tokio/src/runtime/metrics/histogram/h2_histogram.rs @@ -9,6 +9,7 @@ const DEFAULT_MAX_VALUE: Duration = Duration::from_secs(60); /// Default precision is 2^-2 = 25% max error const DEFAULT_PRECISION: u32 = 2; +const MAX_PRECISION: u32 = 10; /// Log Histogram /// @@ -57,6 +58,15 @@ impl LogHistogram { } } + fn truncate_to_max_value(&self, max_value: u64) -> LogHistogram { + let mut hist = self.clone(); + while hist.max_value() >= max_value { + hist.num_buckets -= 1; + } + hist.num_buckets += 1; + hist + } + /// Creates a builder for [`LogHistogram`] pub fn builder() -> LogHistogramBuilder { LogHistogramBuilder::default() @@ -64,8 +74,7 @@ impl LogHistogram { /// The maximum value that can be stored before truncation in this histogram pub fn max_value(&self) -> u64 { - let n = (self.num_buckets / (1 << self.p)) - 1 + self.p as usize; - (1_u64 << n) - 1 + self.bucket_range(self.num_buckets - 2).end } pub(crate) fn value_to_bucket(&self, value: u64) -> usize { @@ -155,23 +164,23 @@ impl LogHistogramBuilder { /// such that `2^-p` is less than `precision`. To set `p` directly, use /// [`LogHistogramBuilder::precision_exact`]. /// + /// Precision controls the size of the "bucket groups" (consecutive buckets with identical + /// ranges). When `p` is 0, each bucket will be twice the size of the previous bucket. To match + /// the behavior of the legacy log histogram implementation, use `builder.precision_exact(0)`. + /// /// The default value is 25% (2^-2) /// /// The highest supported precision is `0.0977%` `(2^-10)`. Provided values /// less than this will be truncated. /// /// # Panics - /// - `precision` < 0 - /// - `precision` > 1 + /// - `max_error` < 0 + /// - `max_error` > 1 pub fn max_error(mut self, max_error: f64) -> Self { - if max_error < 0.0 { - panic!("precision must be >= 0"); - }; - if max_error > 1.0 { - panic!("precision must be > 1"); - }; + assert!(max_error > 0.0, "max_error must be greater than 0"); + assert!(max_error < 1.0, "max_error must be less than 1"); let mut p = 2; - while 2_f64.powf(-1.0 * p as f64) > max_error && p <= 10 { + while 2_f64.powf(-1.0 * p as f64) > max_error && p <= MAX_PRECISION { p += 1; } self.precision = Some(p); @@ -180,16 +189,20 @@ impl LogHistogramBuilder { /// Sets the precision of this histogram directly. /// + /// The precision (meaning: the ratio `n/bucket_range(n)` for some given `n`) will be `2^-p`. + /// + /// Precision controls the number consecutive buckets with identically sized ranges. + /// When `p` is 0, each bucket will be twice the size of the previous bucket (bucket groups are + /// only a single bucket wide). + /// + /// To match the behavior of the legacy implementation ([`HistogramScale::Log`]), use `builder.precision_exact(0)`. + /// /// # Panics - /// - `p` < 2 /// - `p` > 10 + /// + /// [`HistogramScale::Log`]: [crate::runtime::HistogramScale] pub fn precision_exact(mut self, p: u32) -> Self { - if p < 2 { - panic!("precision must be >= 2"); - }; - if p > 10 { - panic!("precision must be <= 10"); - }; + assert!(p <= MAX_PRECISION, "precision must be <= {MAX_PRECISION}"); self.precision = Some(p); self } @@ -234,16 +247,17 @@ impl LogHistogramBuilder { /// Builds the log histogram pub fn build(&self) -> LogHistogram { - let max_value = duration_as_u64(self.max_value.unwrap_or(DEFAULT_MAX_VALUE)); - let max_value = max_value.next_power_of_two(); + let requested_max_value = duration_as_u64(self.max_value.unwrap_or(DEFAULT_MAX_VALUE)); + let max_value = requested_max_value.next_power_of_two(); let min_value = duration_as_u64(self.min_value.unwrap_or(DEFAULT_MIN_VALUE)); let p = self.precision.unwrap_or(DEFAULT_PRECISION); // determine the bucket offset by finding the bucket for the minimum value. We need to lower // this by one to ensure we are at least as granular as requested. let bucket_offset = cmp::max(bucket_index(min_value, p), 1) - 1; // n must be at least as large as p - let n = max_value.ilog2().max(p); + let n = max_value.ilog2().max(p) + 1; LogHistogram::from_n_p(n, p, bucket_offset as usize) + .truncate_to_max_value(requested_max_value) } } @@ -295,17 +309,55 @@ mod test { #[cfg(not(target_family = "wasm"))] mod proptests { use super::*; + use crate::runtime::metrics::batch::duration_as_u64; + use crate::runtime::metrics::histogram::h2_histogram::MAX_PRECISION; use proptest::prelude::*; + use std::time::Duration; + fn valid_log_histogram_strategy() -> impl Strategy { - (2..=50u32, 2..=16u32, 0..100usize).prop_map(|(n, p, bucket_offset)| { + (1..=50u32, 0..=MAX_PRECISION, 0..100usize).prop_map(|(n, p, bucket_offset)| { let p = p.min(n); let base = LogHistogram::from_n_p(n, p, 0); LogHistogram::from_n_p(n, p, bucket_offset.min(base.num_buckets - 1)) }) } + fn log_histogram_settings() -> impl Strategy { + ( + duration_as_u64(Duration::from_nanos(1))..duration_as_u64(Duration::from_secs(20)), + duration_as_u64(Duration::from_secs(1))..duration_as_u64(Duration::from_secs(1000)), + 0..MAX_PRECISION, + ) + } + // test against a wide assortment of different histogram configurations to ensure invariants hold proptest! { + #[test] + fn log_histogram_settings_maintain_invariants((min_value, max_value, p) in log_histogram_settings()) { + if max_value < min_value { + return Ok(()) + } + let (min_value, max_value) = (Duration::from_nanos(min_value), Duration::from_nanos(max_value)); + let histogram = LogHistogram::builder().min_value(min_value).max_value(max_value).precision_exact(p).build(); + let first_bucket_end = Duration::from_nanos(histogram.bucket_range(0).end); + let last_bucket_start = Duration::from_nanos(histogram.bucket_range(histogram.num_buckets - 1).start); + let second_last_bucket_start = Duration::from_nanos(histogram.bucket_range(histogram.num_buckets - 2).start); + prop_assert!( + first_bucket_end <= min_value, + "first bucket {first_bucket_end:?} must be less than {min_value:?}" + ); + prop_assert!( + last_bucket_start > max_value, + "last bucket start ({last_bucket_start:?} must be at least as big as `max_value` ({max_value:?})" + ); + + // We should have the exact right number of buckets. The second to last bucket should be strictly less than max value. + prop_assert!( + second_last_bucket_start < max_value, + "second last bucket end ({second_last_bucket_start:?} must be at least as big as `max_value` ({max_value:?})" + ); + } + #[test] fn proptest_log_histogram_invariants(histogram in valid_log_histogram_strategy()) { // 1. Assert that the first bucket always starts at 0 @@ -469,7 +521,7 @@ mod test { required_bucket_count, } => required_bucket_count, }; - assert_eq!(num_buckets, 27549); + assert_eq!(num_buckets, 27291); } #[test] diff --git a/tokio/src/runtime/mod.rs b/tokio/src/runtime/mod.rs index 82bda0dc597..bc00bc0810d 100644 --- a/tokio/src/runtime/mod.rs +++ b/tokio/src/runtime/mod.rs @@ -36,7 +36,7 @@ //! loop { //! let n = match socket.read(&mut buf).await { //! // socket closed -//! Ok(n) if n == 0 => return, +//! Ok(0) => return, //! Ok(n) => n, //! Err(e) => { //! println!("failed to read from socket; err = {:?}", e); @@ -84,7 +84,7 @@ //! loop { //! let n = match socket.read(&mut buf).await { //! // socket closed -//! Ok(n) if n == 0 => return, +//! Ok(0) => return, //! Ok(n) => n, //! Err(e) => { //! println!("failed to read from socket; err = {:?}", e); diff --git a/tokio/src/runtime/park.rs b/tokio/src/runtime/park.rs index cdc32dac50a..c5c8b1307d0 100644 --- a/tokio/src/runtime/park.rs +++ b/tokio/src/runtime/park.rs @@ -65,12 +65,7 @@ impl ParkThread { pub(crate) fn park_timeout(&mut self, duration: Duration) { #[cfg(loom)] CURRENT_THREAD_PARK_COUNT.with(|count| count.fetch_add(1, SeqCst)); - - // Wasm doesn't have threads, so just sleep. - #[cfg(not(target_family = "wasm"))] self.inner.park_timeout(duration); - #[cfg(target_family = "wasm")] - std::thread::sleep(duration); } pub(crate) fn shutdown(&mut self) { @@ -109,7 +104,7 @@ impl Inner { return; } - Err(actual) => panic!("inconsistent park state; actual = {}", actual), + Err(actual) => panic!("inconsistent park state; actual = {actual}"), } loop { @@ -155,19 +150,27 @@ impl Inner { return; } - Err(actual) => panic!("inconsistent park_timeout state; actual = {}", actual), + Err(actual) => panic!("inconsistent park_timeout state; actual = {actual}"), } + #[cfg(not(all(target_family = "wasm", not(target_feature = "atomics"))))] // Wait with a timeout, and if we spuriously wake up or otherwise wake up // from a notification, we just want to unconditionally set the state back to // empty, either consuming a notification or un-flagging ourselves as // parked. let (_m, _result) = self.condvar.wait_timeout(m, dur).unwrap(); + #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] + // Wasm without atomics doesn't have threads, so just sleep. + { + let _m = m; + std::thread::sleep(dur); + } + match self.state.swap(EMPTY, SeqCst) { NOTIFIED => {} // got a notification, hurray! PARKED => {} // no notification, alas - n => panic!("inconsistent park_timeout state: {}", n), + n => panic!("inconsistent park_timeout state: {n}"), } } diff --git a/tokio/src/runtime/runtime.rs b/tokio/src/runtime/runtime.rs index 242c37e0fbc..ea6bb247941 100644 --- a/tokio/src/runtime/runtime.rs +++ b/tokio/src/runtime/runtime.rs @@ -421,6 +421,7 @@ impl Runtime { /// # Examples /// /// ``` + /// # if cfg!(miri) { return } // Miri reports error when main thread terminated without waiting all remaining threads. /// use tokio::runtime::Runtime; /// use tokio::task; /// diff --git a/tokio/src/runtime/scheduler/multi_thread/park.rs b/tokio/src/runtime/scheduler/multi_thread/park.rs index aacd9012cc3..b00c648e6d3 100644 --- a/tokio/src/runtime/scheduler/multi_thread/park.rs +++ b/tokio/src/runtime/scheduler/multi_thread/park.rs @@ -151,7 +151,7 @@ impl Inner { return; } - Err(actual) => panic!("inconsistent park state; actual = {}", actual), + Err(actual) => panic!("inconsistent park state; actual = {actual}"), } loop { @@ -188,7 +188,7 @@ impl Inner { return; } - Err(actual) => panic!("inconsistent park state; actual = {}", actual), + Err(actual) => panic!("inconsistent park state; actual = {actual}"), } driver.park(handle); @@ -196,7 +196,7 @@ impl Inner { match self.state.swap(EMPTY, SeqCst) { NOTIFIED => {} // got a notification, hurray! PARKED_DRIVER => {} // no notification, alas - n => panic!("inconsistent park_timeout state: {}", n), + n => panic!("inconsistent park_timeout state: {n}"), } } @@ -211,7 +211,7 @@ impl Inner { NOTIFIED => {} // already unparked PARKED_CONDVAR => self.unpark_condvar(), PARKED_DRIVER => driver.unpark(), - actual => panic!("inconsistent state in unpark; actual = {}", actual), + actual => panic!("inconsistent state in unpark; actual = {actual}"), } } diff --git a/tokio/src/runtime/scheduler/multi_thread/queue.rs b/tokio/src/runtime/scheduler/multi_thread/queue.rs index 99ee31ba15b..bf546fde518 100644 --- a/tokio/src/runtime/scheduler/multi_thread/queue.rs +++ b/tokio/src/runtime/scheduler/multi_thread/queue.rs @@ -264,9 +264,7 @@ impl Local { assert_eq!( tail.wrapping_sub(head) as usize, LOCAL_QUEUE_CAPACITY, - "queue is not full; tail = {}; head = {}", - tail, - head + "queue is not full; tail = {tail}; head = {head}" ); let prev = pack(head, head); @@ -490,8 +488,7 @@ impl Steal { assert!( n <= LOCAL_QUEUE_CAPACITY as UnsignedShort / 2, - "actual = {}", - n + "actual = {n}" ); let (first, _) = unpack(next_packed); diff --git a/tokio/src/runtime/signal/mod.rs b/tokio/src/runtime/signal/mod.rs index bc50c6e982c..8055c0965a6 100644 --- a/tokio/src/runtime/signal/mod.rs +++ b/tokio/src/runtime/signal/mod.rs @@ -118,7 +118,7 @@ impl Driver { Ok(0) => panic!("EOF on self-pipe"), Ok(_) => continue, // Keep reading Err(e) if e.kind() == std_io::ErrorKind::WouldBlock => break, - Err(e) => panic!("Bad read on self-pipe: {}", e), + Err(e) => panic!("Bad read on self-pipe: {e}"), } } diff --git a/tokio/src/runtime/task/harness.rs b/tokio/src/runtime/task/harness.rs index 996f0f2d9b4..9bf73b74fbf 100644 --- a/tokio/src/runtime/task/harness.rs +++ b/tokio/src/runtime/task/harness.rs @@ -284,9 +284,11 @@ where } pub(super) fn drop_join_handle_slow(self) { - // Try to unset `JOIN_INTEREST`. This must be done as a first step in + // Try to unset `JOIN_INTEREST` and `JOIN_WAKER`. This must be done as a first step in // case the task concurrently completed. - if self.state().unset_join_interested().is_err() { + let transition = self.state().transition_to_join_handle_dropped(); + + if transition.drop_output { // It is our responsibility to drop the output. This is critical as // the task output may not be `Send` and as such must remain with // the scheduler or `JoinHandle`. i.e. if the output remains in the @@ -301,6 +303,23 @@ where })); } + if transition.drop_waker { + // If the JOIN_WAKER flag is unset at this point, the task is either + // already terminal or not complete so the `JoinHandle` is responsible + // for dropping the waker. + // Safety: + // If the JOIN_WAKER bit is not set the join handle has exclusive + // access to the waker as per rule 2 in task/mod.rs. + // This can only be the case at this point in two scenarios: + // 1. The task completed and the runtime unset `JOIN_WAKER` flag + // after accessing the waker during task completion. So the + // `JoinHandle` is the only one to access the join waker here. + // 2. The task is not completed so the `JoinHandle` was able to unset + // `JOIN_WAKER` bit itself to get mutable access to the waker. + // The runtime will not access the waker when this flag is unset. + unsafe { self.trailer().set_waker(None) }; + } + // Drop the `JoinHandle` reference, possibly deallocating the task self.drop_reference(); } @@ -311,7 +330,6 @@ where fn complete(self) { // The future has completed and its output has been written to the task // stage. We transition from running to complete. - let snapshot = self.state().transition_to_complete(); // We catch panics here in case dropping the future or waking the @@ -320,13 +338,28 @@ where if !snapshot.is_join_interested() { // The `JoinHandle` is not interested in the output of // this task. It is our responsibility to drop the - // output. + // output. The join waker was already dropped by the + // `JoinHandle` before. self.core().drop_future_or_output(); } else if snapshot.is_join_waker_set() { // Notify the waker. Reading the waker field is safe per rule 4 // in task/mod.rs, since the JOIN_WAKER bit is set and the call // to transition_to_complete() above set the COMPLETE bit. self.trailer().wake_join(); + + // Inform the `JoinHandle` that we are done waking the waker by + // unsetting the `JOIN_WAKER` bit. If the `JoinHandle` has + // already been dropped and `JOIN_INTEREST` is unset, then we must + // drop the waker ourselves. + if !self + .state() + .unset_waker_after_complete() + .is_join_interested() + { + // SAFETY: We have COMPLETE=1 and JOIN_INTEREST=0, so + // we have exclusive access to the waker. + unsafe { self.trailer().set_waker(None) }; + } } })); diff --git a/tokio/src/runtime/task/mod.rs b/tokio/src/runtime/task/mod.rs index 33f54003d38..15c5a8f4afe 100644 --- a/tokio/src/runtime/task/mod.rs +++ b/tokio/src/runtime/task/mod.rs @@ -94,16 +94,30 @@ //! `JoinHandle` needs to (i) successfully set `JOIN_WAKER` to zero if it is //! not already zero to gain exclusive access to the waker field per rule //! 2, (ii) write a waker, and (iii) successfully set `JOIN_WAKER` to one. +//! If the `JoinHandle` unsets `JOIN_WAKER` in the process of being dropped +//! to clear the waker field, only steps (i) and (ii) are relevant. //! //! 6. The `JoinHandle` can change `JOIN_WAKER` only if COMPLETE is zero (i.e. -//! the task hasn't yet completed). +//! the task hasn't yet completed). The runtime can change `JOIN_WAKER` only +//! if COMPLETE is one. +//! +//! 7. If `JOIN_INTEREST` is zero and COMPLETE is one, then the runtime has +//! exclusive (mutable) access to the waker field. This might happen if the +//! `JoinHandle` gets dropped right after the task completes and the runtime +//! sets the `COMPLETE` bit. In this case the runtime needs the mutable access +//! to the waker field to drop it. //! //! Rule 6 implies that the steps (i) or (iii) of rule 5 may fail due to a //! race. If step (i) fails, then the attempt to write a waker is aborted. If //! step (iii) fails because COMPLETE is set to one by another thread after //! step (i), then the waker field is cleared. Once COMPLETE is one (i.e. //! task has completed), the `JoinHandle` will not modify `JOIN_WAKER`. After the -//! runtime sets COMPLETE to one, it invokes the waker if there is one. +//! runtime sets COMPLETE to one, it invokes the waker if there is one so in this +//! case when a task completes the `JOIN_WAKER` bit implicates to the runtime +//! whether it should invoke the waker or not. After the runtime is done with +//! using the waker during task completion, it unsets the `JOIN_WAKER` bit to give +//! the `JoinHandle` exclusive access again so that it is able to drop the waker +//! at a later point. //! //! All other fields are immutable and can be accessed immutably without //! synchronization by anyone. diff --git a/tokio/src/runtime/task/state.rs b/tokio/src/runtime/task/state.rs index 0fc7bb0329b..da3e8d38d91 100644 --- a/tokio/src/runtime/task/state.rs +++ b/tokio/src/runtime/task/state.rs @@ -89,6 +89,12 @@ pub(crate) enum TransitionToNotifiedByRef { Submit, } +#[must_use] +pub(super) struct TransitionToJoinHandleDrop { + pub(super) drop_waker: bool, + pub(super) drop_output: bool, +} + /// All transitions are performed via RMW operations. This establishes an /// unambiguous modification order. impl State { @@ -371,22 +377,45 @@ impl State { .map_err(|_| ()) } - /// Tries to unset the `JOIN_INTEREST` flag. - /// - /// Returns `Ok` if the operation happens before the task transitions to a - /// completed state, `Err` otherwise. - pub(super) fn unset_join_interested(&self) -> UpdateResult { - self.fetch_update(|curr| { - assert!(curr.is_join_interested()); + /// Unsets the `JOIN_INTEREST` flag. If `COMPLETE` is not set, the `JOIN_WAKER` + /// flag is also unset. + /// The returned `TransitionToJoinHandleDrop` indicates whether the `JoinHandle` should drop + /// the output of the future or the join waker after the transition. + pub(super) fn transition_to_join_handle_dropped(&self) -> TransitionToJoinHandleDrop { + self.fetch_update_action(|mut snapshot| { + assert!(snapshot.is_join_interested()); - if curr.is_complete() { - return None; + let mut transition = TransitionToJoinHandleDrop { + drop_waker: false, + drop_output: false, + }; + + snapshot.unset_join_interested(); + + if !snapshot.is_complete() { + // If `COMPLETE` is unset we also unset `JOIN_WAKER` to give the + // `JoinHandle` exclusive access to the waker following rule 6 in task/mod.rs. + // The `JoinHandle` will drop the waker if it has exclusive access + // to drop it. + snapshot.unset_join_waker(); + } else { + // If `COMPLETE` is set the task is completed so the `JoinHandle` is responsible + // for dropping the output. + transition.drop_output = true; } - let mut next = curr; - next.unset_join_interested(); + if !snapshot.is_join_waker_set() { + // If the `JOIN_WAKER` bit is unset and the `JOIN_HANDLE` has exclusive access to + // the join waker and should drop it following this transition. + // This might happen in two situations: + // 1. The task is not completed and we just unset the `JOIN_WAKer` above in this + // function. + // 2. The task is completed. In that case the `JOIN_WAKER` bit was already unset + // by the runtime during completion. + transition.drop_waker = true; + } - Some(next) + (transition, Some(snapshot)) }) } @@ -417,12 +446,15 @@ impl State { pub(super) fn unset_waker(&self) -> UpdateResult { self.fetch_update(|curr| { assert!(curr.is_join_interested()); - assert!(curr.is_join_waker_set()); if curr.is_complete() { return None; } + // If the task is completed, this bit may have been unset by + // `unset_waker_after_complete`. + assert!(curr.is_join_waker_set()); + let mut next = curr; next.unset_join_waker(); @@ -430,6 +462,16 @@ impl State { }) } + /// Unsets the `JOIN_WAKER` bit unconditionally after task completion. + /// + /// This operation requires the task to be completed. + pub(super) fn unset_waker_after_complete(&self) -> Snapshot { + let prev = Snapshot(self.val.fetch_and(!JOIN_WAKER, AcqRel)); + assert!(prev.is_complete()); + assert!(prev.is_join_waker_set()); + Snapshot(prev.0 & !JOIN_WAKER) + } + pub(super) fn ref_inc(&self) { use std::process; use std::sync::atomic::Ordering::Relaxed; diff --git a/tokio/src/runtime/task/trace/mod.rs b/tokio/src/runtime/task/trace/mod.rs index bb411f42d72..71aa3b22657 100644 --- a/tokio/src/runtime/task/trace/mod.rs +++ b/tokio/src/runtime/task/trace/mod.rs @@ -24,7 +24,7 @@ use super::{Notified, OwnedTasks, Schedule}; type Backtrace = Vec; type SymbolTrace = Vec; -/// The ambiant backtracing context. +/// The ambient backtracing context. pub(crate) struct Context { /// The address of [`Trace::root`] establishes an upper unwinding bound on /// the backtraces in `Trace`. @@ -56,7 +56,8 @@ pub(crate) struct Trace { pin_project_lite::pin_project! { #[derive(Debug, Clone)] #[must_use = "futures do nothing unless you `.await` or poll them"] - pub(crate) struct Root { + /// A future wrapper that roots traces (captured with [`Trace::capture`]). + pub struct Root { #[pin] future: T, } @@ -138,6 +139,10 @@ impl Trace { pub(crate) fn root(future: F) -> Root { Root { future } } + + pub(crate) fn backtraces(&self) -> &[Backtrace] { + &self.backtraces + } } /// If this is a sub-invocation of [`Trace::capture`], capture a backtrace. diff --git a/tokio/src/runtime/task_hooks.rs b/tokio/src/runtime/task_hooks.rs index bc505ed16b4..2c884af74be 100644 --- a/tokio/src/runtime/task_hooks.rs +++ b/tokio/src/runtime/task_hooks.rs @@ -16,11 +16,11 @@ pub(crate) struct TaskHooks { /// Task metadata supplied to user-provided hooks for task events. /// -/// **Note**: This is an [unstable API][unstable]. The public API of this type -/// may break in 1.x releases. See [the documentation on unstable -/// features][unstable] for details. -/// -/// [unstable]: crate#unstable-features +/// **Note**: This is an [unstable API][unstable]. The public API of this type +/// may break in 1.x releases. See [the documentation on unstable +/// features][unstable] for details. +/// +/// [unstable]: crate#unstable-features #[allow(missing_debug_implementations)] #[cfg_attr(not(tokio_unstable), allow(unreachable_pub))] pub struct TaskMeta<'a> { diff --git a/tokio/src/runtime/tests/loom_current_thread.rs b/tokio/src/runtime/tests/loom_current_thread.rs index edda6e49954..fd0a44314f8 100644 --- a/tokio/src/runtime/tests/loom_current_thread.rs +++ b/tokio/src/runtime/tests/loom_current_thread.rs @@ -1,6 +1,6 @@ mod yield_now; -use crate::loom::sync::atomic::AtomicUsize; +use crate::loom::sync::atomic::{AtomicUsize, Ordering}; use crate::loom::sync::Arc; use crate::loom::thread; use crate::runtime::{Builder, Runtime}; @@ -9,7 +9,7 @@ use crate::task; use std::future::Future; use std::pin::Pin; use std::sync::atomic::Ordering::{Acquire, Release}; -use std::task::{Context, Poll}; +use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; fn assert_at_most_num_polls(rt: Arc, at_most_polls: usize) { let (tx, rx) = oneshot::channel(); @@ -106,6 +106,60 @@ fn assert_no_unnecessary_polls() { }); } +#[test] +fn drop_jh_during_schedule() { + unsafe fn waker_clone(ptr: *const ()) -> RawWaker { + let atomic = unsafe { &*(ptr as *const AtomicUsize) }; + atomic.fetch_add(1, Ordering::Relaxed); + RawWaker::new(ptr, &VTABLE) + } + unsafe fn waker_drop(ptr: *const ()) { + let atomic = unsafe { &*(ptr as *const AtomicUsize) }; + atomic.fetch_sub(1, Ordering::Relaxed); + } + unsafe fn waker_nop(_ptr: *const ()) {} + + static VTABLE: RawWakerVTable = + RawWakerVTable::new(waker_clone, waker_drop, waker_nop, waker_drop); + + loom::model(|| { + let rt = Builder::new_current_thread().build().unwrap(); + + let mut jh = rt.spawn(async {}); + // Using AbortHandle to increment task refcount. This ensures that the waker is not + // destroyed due to the refcount hitting zero. + let task_refcnt = jh.abort_handle(); + + let waker_refcnt = AtomicUsize::new(1); + { + // Set up the join waker. + use std::future::Future; + use std::pin::Pin; + + // SAFETY: Before `waker_refcnt` goes out of scope, this test asserts that the refcnt + // has dropped to zero. + let join_waker = unsafe { + Waker::from_raw(RawWaker::new( + (&waker_refcnt) as *const AtomicUsize as *const (), + &VTABLE, + )) + }; + + assert!(Pin::new(&mut jh) + .poll(&mut Context::from_waker(&join_waker)) + .is_pending()); + } + assert_eq!(waker_refcnt.load(Ordering::Relaxed), 1); + + let bg_thread = loom::thread::spawn(move || drop(jh)); + rt.block_on(crate::task::yield_now()); + bg_thread.join().unwrap(); + + assert_eq!(waker_refcnt.load(Ordering::Relaxed), 0); + drop(task_refcnt); + }); +} + struct BlockedFuture { rx: Receiver<()>, num_polls: Arc, diff --git a/tokio/src/runtime/time/wheel/mod.rs b/tokio/src/runtime/time/wheel/mod.rs index f2b4228514c..7040fc146b1 100644 --- a/tokio/src/runtime/time/wheel/mod.rs +++ b/tokio/src/runtime/time/wheel/mod.rs @@ -298,13 +298,7 @@ mod test { #[test] fn test_level_for() { for pos in 0..64 { - assert_eq!( - 0, - level_for(0, pos), - "level_for({}) -- binary = {:b}", - pos, - pos - ); + assert_eq!(0, level_for(0, pos), "level_for({pos}) -- binary = {pos:b}"); } for level in 1..5 { @@ -313,9 +307,7 @@ mod test { assert_eq!( level, level_for(0, a as u64), - "level_for({}) -- binary = {:b}", - a, - a + "level_for({a}) -- binary = {a:b}" ); if pos > level { @@ -323,9 +315,7 @@ mod test { assert_eq!( level, level_for(0, a as u64), - "level_for({}) -- binary = {:b}", - a, - a + "level_for({a}) -- binary = {a:b}" ); } @@ -334,9 +324,7 @@ mod test { assert_eq!( level, level_for(0, a as u64), - "level_for({}) -- binary = {:b}", - a, - a + "level_for({a}) -- binary = {a:b}" ); } } diff --git a/tokio/src/signal/registry.rs b/tokio/src/signal/registry.rs index 3fff8df9303..e5358cae324 100644 --- a/tokio/src/signal/registry.rs +++ b/tokio/src/signal/registry.rs @@ -76,7 +76,7 @@ impl Registry { fn register_listener(&self, event_id: EventId) -> watch::Receiver<()> { self.storage .event_info(event_id) - .unwrap_or_else(|| panic!("invalid event_id: {}", event_id)) + .unwrap_or_else(|| panic!("invalid event_id: {event_id}")) .tx .subscribe() } diff --git a/tokio/src/signal/unix.rs b/tokio/src/signal/unix.rs index ae6bc94eae8..e70863b54b7 100644 --- a/tokio/src/signal/unix.rs +++ b/tokio/src/signal/unix.rs @@ -23,11 +23,13 @@ pub(crate) type OsStorage = Box<[SignalInfo]>; impl Init for OsStorage { fn init() -> Self { // There are reliable signals ranging from 1 to 33 available on every Unix platform. - #[cfg(not(target_os = "linux"))] + #[cfg(not(any(target_os = "linux", target_os = "illumos")))] let possible = 0..=33; - // On Linux, there are additional real-time signals available. - #[cfg(target_os = "linux")] + // On Linux and illumos, there are additional real-time signals + // available. (This is also likely true on Solaris, but this should be + // verified before being enabled.) + #[cfg(any(target_os = "linux", target_os = "illumos"))] let possible = 0..=libc::SIGRTMAX(); possible.map(|_| SignalInfo::default()).collect() @@ -130,7 +132,8 @@ impl SignalKind { target_os = "freebsd", target_os = "macos", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_os = "illumos" ))] pub const fn info() -> Self { Self(libc::SIGINFO) @@ -144,6 +147,15 @@ impl SignalKind { Self(libc::SIGINT) } + #[cfg(target_os = "haiku")] + /// Represents the `SIGPOLL` signal. + /// + /// On POSIX systems this signal is sent when I/O operations are possible + /// on some file descriptor. By default, this signal is ignored. + pub const fn io() -> Self { + Self(libc::SIGPOLL) + } + #[cfg(not(target_os = "haiku"))] /// Represents the `SIGIO` signal. /// /// On Unix systems this signal is sent when I/O operations are possible @@ -258,7 +270,7 @@ fn signal_enable(signal: SignalKind, handle: &Handle) -> io::Result<()> { if signal < 0 || signal_hook_registry::FORBIDDEN.contains(&signal) { return Err(Error::new( ErrorKind::Other, - format!("Refusing to register signal {}", signal), + format!("Refusing to register signal {signal}"), )); } diff --git a/tokio/src/signal/windows.rs b/tokio/src/signal/windows.rs index d8af9b4c9d9..43dab808bba 100644 --- a/tokio/src/signal/windows.rs +++ b/tokio/src/signal/windows.rs @@ -12,13 +12,15 @@ use crate::signal::RxFuture; use std::io; use std::task::{Context, Poll}; -#[cfg(not(docsrs))] +#[cfg(windows)] #[path = "windows/sys.rs"] mod imp; -#[cfg(not(docsrs))] + +#[cfg(windows)] pub(crate) use self::imp::{OsExtraData, OsStorage}; -#[cfg(docsrs)] +// For building documentation on Unix machines when the `docsrs` flag is set. +#[cfg(not(windows))] #[path = "windows/stub.rs"] mod imp; diff --git a/tokio/src/sync/broadcast.rs b/tokio/src/sync/broadcast.rs index 56c4cd6b92f..e48925b497e 100644 --- a/tokio/src/sync/broadcast.rs +++ b/tokio/src/sync/broadcast.rs @@ -255,7 +255,7 @@ pub mod error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { RecvError::Closed => write!(f, "channel closed"), - RecvError::Lagged(amt) => write!(f, "channel lagged by {}", amt), + RecvError::Lagged(amt) => write!(f, "channel lagged by {amt}"), } } } @@ -291,7 +291,7 @@ pub mod error { match self { TryRecvError::Empty => write!(f, "channel empty"), TryRecvError::Closed => write!(f, "channel closed"), - TryRecvError::Lagged(amt) => write!(f, "channel lagged by {}", amt), + TryRecvError::Lagged(amt) => write!(f, "channel lagged by {amt}"), } } } @@ -301,6 +301,8 @@ pub mod error { use self::error::{RecvError, SendError, TryRecvError}; +use super::Notify; + /// Data shared between senders and receivers. struct Shared { /// slots in the channel. @@ -314,6 +316,9 @@ struct Shared { /// Number of outstanding Sender handles. num_tx: AtomicUsize, + + /// Notify when the last subscribed [`Receiver`] drops. + notify_last_rx_drop: Notify, } /// Next position to write a value. @@ -528,6 +533,7 @@ impl Sender { waiters: LinkedList::new(), }), num_tx: AtomicUsize::new(1), + notify_last_rx_drop: Notify::new(), }); Sender { shared } @@ -805,6 +811,47 @@ impl Sender { Arc::ptr_eq(&self.shared, &other.shared) } + /// A future which completes when the number of [Receiver]s subscribed to this `Sender` reaches + /// zero. + /// + /// # Examples + /// + /// ``` + /// use futures::FutureExt; + /// use tokio::sync::broadcast; + /// + /// #[tokio::main] + /// async fn main() { + /// let (tx, mut rx1) = broadcast::channel::(16); + /// let mut rx2 = tx.subscribe(); + /// + /// let _ = tx.send(10); + /// + /// assert_eq!(rx1.recv().await.unwrap(), 10); + /// drop(rx1); + /// assert!(tx.closed().now_or_never().is_none()); + /// + /// assert_eq!(rx2.recv().await.unwrap(), 10); + /// drop(rx2); + /// assert!(tx.closed().now_or_never().is_some()); + /// } + /// ``` + pub async fn closed(&self) { + loop { + let notified = self.shared.notify_last_rx_drop.notified(); + + { + // Ensure the lock drops if the channel isn't closed + let tail = self.shared.tail.lock(); + if tail.closed { + return; + } + } + + notified.await; + } + } + fn close_channel(&self) { let mut tail = self.shared.tail.lock(); tail.closed = true; @@ -819,8 +866,14 @@ fn new_receiver(shared: Arc>) -> Receiver { assert!(tail.rx_cnt != MAX_RECEIVERS, "max receivers"); - tail.rx_cnt = tail.rx_cnt.checked_add(1).expect("overflow"); + if tail.rx_cnt == 0 { + // Potentially need to re-open the channel, if a new receiver has been added between calls + // to poll(). Note that we use rx_cnt == 0 instead of is_closed since is_closed also + // applies if the sender has been dropped + tail.closed = false; + } + tail.rx_cnt = tail.rx_cnt.checked_add(1).expect("overflow"); let next = tail.pos; drop(tail); @@ -1346,6 +1399,12 @@ impl Drop for Receiver { tail.rx_cnt -= 1; let until = tail.pos; + let remaining_rx = tail.rx_cnt; + + if remaining_rx == 0 { + self.shared.notify_last_rx_drop.notify_waiters(); + tail.closed = true; + } drop(tail); diff --git a/tokio/src/sync/mpsc/chan.rs b/tokio/src/sync/mpsc/chan.rs index f4cedf0d4dd..1e6eaab1798 100644 --- a/tokio/src/sync/mpsc/chan.rs +++ b/tokio/src/sync/mpsc/chan.rs @@ -490,10 +490,34 @@ impl Drop for Rx { self.inner.rx_fields.with_mut(|rx_fields_ptr| { let rx_fields = unsafe { &mut *rx_fields_ptr }; + struct Guard<'a, T, S: Semaphore> { + list: &'a mut list::Rx, + tx: &'a list::Tx, + sem: &'a S, + } + + impl<'a, T, S: Semaphore> Guard<'a, T, S> { + fn drain(&mut self) { + // call T's destructor. + while let Some(Value(_)) = self.list.pop(self.tx) { + self.sem.add_permit(); + } + } + } - while let Some(Value(_)) = rx_fields.list.pop(&self.inner.tx) { - self.inner.semaphore.add_permit(); + impl<'a, T, S: Semaphore> Drop for Guard<'a, T, S> { + fn drop(&mut self) { + self.drain(); + } } + + let mut guard = Guard { + list: &mut rx_fields.list, + tx: &self.inner.tx, + sem: &self.inner.semaphore, + }; + + guard.drain(); }); } } diff --git a/tokio/src/sync/once_cell.rs b/tokio/src/sync/once_cell.rs index 82b8259c667..1b723048dff 100644 --- a/tokio/src/sync/once_cell.rs +++ b/tokio/src/sync/once_cell.rs @@ -9,7 +9,7 @@ use std::ptr; use std::sync::atomic::{AtomicBool, Ordering}; // This file contains an implementation of an OnceCell. The principle -// behind the safety the of the cell is that any thread with an `&OnceCell` may +// behind the safety of the cell is that any thread with an `&OnceCell` may // access the `value` field according the following rules: // // 1. When `value_set` is false, the `value` field may be modified by the @@ -179,8 +179,8 @@ impl OnceCell { /// /// [`OnceCell::new`]: crate::sync::OnceCell::new // Once https://github.com/rust-lang/rust/issues/73255 lands - // and tokio MSRV is bumped to the rustc version with it stablised, - // we can made this function available in const context, + // and tokio MSRV is bumped to the rustc version with it stabilised, + // we can make this function available in const context, // by creating `Semaphore::const_new_closed`. pub fn new_with(value: Option) -> Self { if let Some(v) = value { diff --git a/tokio/src/sync/rwlock.rs b/tokio/src/sync/rwlock.rs index 14983d5cb32..ff02c7971d6 100644 --- a/tokio/src/sync/rwlock.rs +++ b/tokio/src/sync/rwlock.rs @@ -274,8 +274,7 @@ impl RwLock { { assert!( max_reads <= MAX_READS, - "a RwLock may not be created with more than {} readers", - MAX_READS + "a RwLock may not be created with more than {MAX_READS} readers" ); #[cfg(all(tokio_unstable, feature = "tracing"))] diff --git a/tokio/src/sync/watch.rs b/tokio/src/sync/watch.rs index 7d042a6f950..0f3bafff889 100644 --- a/tokio/src/sync/watch.rs +++ b/tokio/src/sync/watch.rs @@ -767,44 +767,51 @@ impl Receiver { /// When this function returns, the value that was passed to the closure /// when it returned `true` will be considered seen. /// - /// If the channel is closed, then `wait_for` will return a `RecvError`. + /// If the channel is closed, then `wait_for` will return a [`RecvError`]. /// Once this happens, no more messages can ever be sent on the channel. /// When an error is returned, it is guaranteed that the closure has been /// called on the last value, and that it returned `false` for that value. /// (If the closure returned `true`, then the last value would have been /// returned instead of the error.) /// - /// Like the `borrow` method, the returned borrow holds a read lock on the + /// Like the [`borrow`] method, the returned borrow holds a read lock on the /// inner value. This means that long-lived borrows could cause the producer /// half to block. It is recommended to keep the borrow as short-lived as /// possible. See the documentation of `borrow` for more information on /// this. /// - /// [`Receiver::changed()`]: crate::sync::watch::Receiver::changed + /// [`borrow`]: Receiver::borrow + /// [`RecvError`]: error::RecvError + /// + /// # Cancel safety + /// + /// This method is cancel safe. If you use it as the event in a + /// [`tokio::select!`](crate::select) statement and some other branch + /// completes first, then it is guaranteed that the last seen value `val` + /// (if any) satisfies `f(val) == false`. + /// + /// # Panics + /// + /// If and only if the closure `f` panics. In that case, no resource owned + /// or shared by this [`Receiver`] will be poisoned. /// /// # Examples /// /// ``` /// use tokio::sync::watch; + /// use tokio::time::{sleep, Duration}; /// - /// #[tokio::main] - /// + /// #[tokio::main(flavor = "current_thread", start_paused = true)] /// async fn main() { - /// let (tx, _rx) = watch::channel("hello"); + /// let (tx, mut rx) = watch::channel("hello"); /// - /// tx.send("goodbye").unwrap(); + /// tokio::spawn(async move { + /// sleep(Duration::from_secs(1)).await; + /// tx.send("goodbye").unwrap(); + /// }); /// - /// // here we subscribe to a second receiver - /// // now in case of using `changed` we would have - /// // to first check the current value and then wait - /// // for changes or else `changed` would hang. - /// let mut rx2 = tx.subscribe(); - /// - /// // in place of changed we have use `wait_for` - /// // which would automatically check the current value - /// // and wait for changes until the closure returns true. - /// assert!(rx2.wait_for(|val| *val == "goodbye").await.is_ok()); - /// assert_eq!(*rx2.borrow(), "goodbye"); + /// assert!(rx.wait_for(|val| *val == "goodbye").await.is_ok()); + /// assert_eq!(*rx.borrow(), "goodbye"); /// } /// ``` pub async fn wait_for( diff --git a/tokio/src/task/join_set.rs b/tokio/src/task/join_set.rs index b3d64b9b872..ed2777ccf86 100644 --- a/tokio/src/task/join_set.rs +++ b/tokio/src/task/join_set.rs @@ -395,9 +395,9 @@ impl JoinSet { /// tokio::time::sleep(Duration::from_secs(3 - i)).await; /// i /// }); - /// } + /// } /// - /// let output = set.join_all().await; + /// let output = set.join_all().await; /// assert_eq!(output, vec![2, 1, 0]); /// } /// ``` @@ -414,8 +414,8 @@ impl JoinSet { /// /// for i in 0..3 { /// set.spawn(async move {i}); - /// } - /// + /// } + /// /// let mut output = Vec::new(); /// while let Some(res) = set.join_next().await{ /// match res { diff --git a/tokio/src/time/error.rs b/tokio/src/time/error.rs index 3d6025f5f29..21920059090 100644 --- a/tokio/src/time/error.rs +++ b/tokio/src/time/error.rs @@ -96,7 +96,7 @@ impl fmt::Display for Error { Kind::AtCapacity => "timer is at capacity and cannot create a new entry", Kind::Invalid => "timer duration exceeds maximum duration", }; - write!(fmt, "{}", descr) + write!(fmt, "{descr}") } } diff --git a/tokio/src/time/instant.rs b/tokio/src/time/instant.rs index 14cf6e567b5..44955dc9878 100644 --- a/tokio/src/time/instant.rs +++ b/tokio/src/time/instant.rs @@ -116,7 +116,7 @@ impl Instant { } /// Returns the amount of time elapsed since this instant was created, - /// or zero duration if that this instant is in the future. + /// or zero duration if this instant is in the future. /// /// # Examples /// diff --git a/tokio/src/time/sleep.rs b/tokio/src/time/sleep.rs index 7e393d0d17a..6e59f1ff3d6 100644 --- a/tokio/src/time/sleep.rs +++ b/tokio/src/time/sleep.rs @@ -447,7 +447,7 @@ impl Future for Sleep { let _ao_poll_span = self.inner.ctx.async_op_poll_span.clone().entered(); match ready!(self.as_mut().poll_elapsed(cx)) { Ok(()) => Poll::Ready(()), - Err(e) => panic!("timer error: {}", e), + Err(e) => panic!("timer error: {e}"), } } } diff --git a/tokio/tests/_require_full.rs b/tokio/tests/_require_full.rs index 81c25179615..d33943a960d 100644 --- a/tokio/tests/_require_full.rs +++ b/tokio/tests/_require_full.rs @@ -1,5 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] - #[cfg(not(any(feature = "full", target_family = "wasm")))] compile_error!("run main Tokio tests with `--features full`"); diff --git a/tokio/tests/buffered.rs b/tokio/tests/buffered.rs index bb67255f57d..6e111801015 100644 --- a/tokio/tests/buffered.rs +++ b/tokio/tests/buffered.rs @@ -9,7 +9,7 @@ use std::net::TcpStream; use std::thread; #[tokio::test] -#[cfg_attr(miri, ignore)] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn echo_server() { const N: usize = 1024; diff --git a/tokio/tests/coop_budget.rs b/tokio/tests/coop_budget.rs index 2400187d0d5..3aaf9db21d6 100644 --- a/tokio/tests/coop_budget.rs +++ b/tokio/tests/coop_budget.rs @@ -22,7 +22,7 @@ use tokio::net::UdpSocket; /// Since we are both sending and receiving, that should happen once per 64 packets, because budgets are of size 128 /// and there are two budget events per packet, a send and a recv. #[tokio::test] -#[cfg_attr(miri, ignore)] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn coop_budget_udp_send_recv() { const BUDGET: usize = 128; const N_ITERATIONS: usize = 1024; diff --git a/tokio/tests/dump.rs b/tokio/tests/dump.rs index 68b53aaf291..c946f38436c 100644 --- a/tokio/tests/dump.rs +++ b/tokio/tests/dump.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![cfg(all( tokio_unstable, tokio_taskdump, diff --git a/tokio/tests/fs_copy.rs b/tokio/tests/fs_copy.rs index 4f8daadf269..fac64dccddc 100644 --- a/tokio/tests/fs_copy.rs +++ b/tokio/tests/fs_copy.rs @@ -5,7 +5,7 @@ use tempfile::tempdir; use tokio::fs; #[tokio::test] -#[cfg_attr(miri, ignore)] +#[cfg_attr(miri, ignore)] // No `fchmod` in miri. async fn copy() { let dir = tempdir().unwrap(); @@ -22,7 +22,7 @@ async fn copy() { } #[tokio::test] -#[cfg_attr(miri, ignore)] +#[cfg_attr(miri, ignore)] // No `fchmod` in miri. async fn copy_permissions() { let dir = tempdir().unwrap(); let from_path = dir.path().join("foo.txt"); diff --git a/tokio/tests/fs_file.rs b/tokio/tests/fs_file.rs index 5eb43265e89..d066823d857 100644 --- a/tokio/tests/fs_file.rs +++ b/tokio/tests/fs_file.rs @@ -211,7 +211,7 @@ async fn file_debug_fmt() { let file = File::open(tempfile.path()).await.unwrap(); assert_eq!( - &format!("{:?}", file)[0..33], + &format!("{file:?}")[0..33], "tokio::fs::File { std: File { fd:" ); } diff --git a/tokio/tests/fs_link.rs b/tokio/tests/fs_link.rs index 36cd1fe0d05..4e6742a57f8 100644 --- a/tokio/tests/fs_link.rs +++ b/tokio/tests/fs_link.rs @@ -7,7 +7,7 @@ use std::io::Write; use tempfile::tempdir; #[tokio::test] -#[cfg_attr(miri, ignore)] +#[cfg_attr(miri, ignore)] // No `linkat` in miri. async fn test_hard_link() { let dir = tempdir().unwrap(); let src = dir.path().join("src.txt"); diff --git a/tokio/tests/fs_open_options.rs b/tokio/tests/fs_open_options.rs index 84b63a504cf..58d7de647e2 100644 --- a/tokio/tests/fs_open_options.rs +++ b/tokio/tests/fs_open_options.rs @@ -59,8 +59,7 @@ async fn open_options_mode() { // TESTING HACK: use Debug output to check the stored data assert!( mode.contains("mode: 420 ") || mode.contains("mode: 0o000644 "), - "mode is: {}", - mode + "mode is: {mode}" ); } diff --git a/tokio/tests/fs_try_exists.rs b/tokio/tests/fs_try_exists.rs index 56d4bca18a4..5c855341767 100644 --- a/tokio/tests/fs_try_exists.rs +++ b/tokio/tests/fs_try_exists.rs @@ -5,7 +5,7 @@ use tempfile::tempdir; use tokio::fs; #[tokio::test] -#[cfg_attr(miri, ignore)] +#[cfg_attr(miri, ignore)] // No `chmod` in miri. async fn try_exists() { let dir = tempdir().unwrap(); diff --git a/tokio/tests/io_async_fd.rs b/tokio/tests/io_async_fd.rs index 469d2a4acbe..ab8893f237a 100644 --- a/tokio/tests/io_async_fd.rs +++ b/tokio/tests/io_async_fd.rs @@ -1,5 +1,5 @@ #![warn(rust_2018_idioms)] -#![cfg(all(unix, feature = "full", not(miri)))] +#![cfg(all(unix, feature = "full"))] use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::{ @@ -141,13 +141,14 @@ fn drain(mut fd: &FileDescriptor, mut amt: usize) { match fd.read(&mut buf[..]) { Err(e) if e.kind() == ErrorKind::WouldBlock => {} Ok(0) => panic!("unexpected EOF"), - Err(e) => panic!("unexpected error: {:?}", e), + Err(e) => panic!("unexpected error: {e:?}"), Ok(x) => amt -= x, } } } #[tokio::test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. async fn initially_writable() { let (a, b) = socketpair(); @@ -166,6 +167,7 @@ async fn initially_writable() { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. async fn reset_readable() { let (a, mut b) = socketpair(); @@ -210,6 +212,7 @@ async fn reset_readable() { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. async fn reset_writable() { let (a, b) = socketpair(); @@ -247,6 +250,7 @@ impl AsRawFd for ArcFd { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. async fn drop_closes() { let (a, mut b) = socketpair(); @@ -287,6 +291,7 @@ async fn drop_closes() { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. async fn reregister() { let (a, _b) = socketpair(); @@ -296,7 +301,8 @@ async fn reregister() { } #[tokio::test] -async fn try_io() { +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. +async fn guard_try_io() { let (a, mut b) = socketpair(); b.write_all(b"0").unwrap(); @@ -331,6 +337,109 @@ async fn try_io() { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. +async fn try_io_readable() { + let (a, mut b) = socketpair(); + let mut afd_a = AsyncFd::new(a).unwrap(); + + // Give the runtime some time to update bookkeeping. + tokio::task::yield_now().await; + + { + let mut called = false; + let _ = afd_a.try_io_mut(Interest::READABLE, |_| { + called = true; + Ok(()) + }); + assert!( + !called, + "closure should not have been called, since socket should not be readable" + ); + } + + // Make `a` readable by writing to `b`. + // Give the runtime some time to update bookkeeping. + b.write_all(&[0]).unwrap(); + tokio::task::yield_now().await; + + { + let mut called = false; + let _ = afd_a.try_io(Interest::READABLE, |_| { + called = true; + Ok(()) + }); + assert!( + called, + "closure should have been called, since socket should have data available to read" + ); + } + + { + let mut called = false; + let _ = afd_a.try_io(Interest::READABLE, |_| { + called = true; + io::Result::<()>::Err(ErrorKind::WouldBlock.into()) + }); + assert!( + called, + "closure should have been called, since socket should have data available to read" + ); + } + + { + let mut called = false; + let _ = afd_a.try_io(Interest::READABLE, |_| { + called = true; + Ok(()) + }); + assert!(!called, "closure should not have been called, since socket readable state should have been cleared"); + } +} + +#[tokio::test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. +async fn try_io_writable() { + let (a, _b) = socketpair(); + let afd_a = AsyncFd::new(a).unwrap(); + + // Give the runtime some time to update bookkeeping. + tokio::task::yield_now().await; + + { + let mut called = false; + let _ = afd_a.try_io(Interest::WRITABLE, |_| { + called = true; + Ok(()) + }); + assert!( + called, + "closure should have been called, since socket should still be marked as writable" + ); + } + { + let mut called = false; + let _ = afd_a.try_io(Interest::WRITABLE, |_| { + called = true; + io::Result::<()>::Err(ErrorKind::WouldBlock.into()) + }); + assert!( + called, + "closure should have been called, since socket should still be marked as writable" + ); + } + + { + let mut called = false; + let _ = afd_a.try_io(Interest::WRITABLE, |_| { + called = true; + Ok(()) + }); + assert!(!called, "closure should not have been called, since socket writable state should have been cleared"); + } +} + +#[tokio::test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. async fn multiple_waiters() { let (a, mut b) = socketpair(); let afd_a = Arc::new(AsyncFd::new(a).unwrap()); @@ -379,6 +488,7 @@ async fn multiple_waiters() { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. async fn poll_fns() { let (a, b) = socketpair(); let afd_a = Arc::new(AsyncFd::new(a).unwrap()); @@ -472,6 +582,7 @@ fn rt() -> tokio::runtime::Runtime { } #[test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. fn driver_shutdown_wakes_currently_pending() { let rt = rt(); @@ -493,6 +604,7 @@ fn driver_shutdown_wakes_currently_pending() { } #[test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. fn driver_shutdown_wakes_future_pending() { let rt = rt(); @@ -508,6 +620,7 @@ fn driver_shutdown_wakes_future_pending() { } #[test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. fn driver_shutdown_wakes_pending_race() { // TODO: make this a loom test for _ in 0..100 { @@ -538,6 +651,7 @@ async fn poll_writable(fd: &AsyncFd) -> std::io::Result(stream: &S, data: &[u8]) -> io::Result { } #[tokio::test] -#[cfg_attr(miri, ignore)] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. async fn clear_ready_matching_clears_ready() { use tokio::io::{Interest, Ready}; @@ -679,7 +797,7 @@ async fn clear_ready_matching_clears_ready() { } #[tokio::test] -#[cfg_attr(miri, ignore)] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. async fn clear_ready_matching_clears_ready_mut() { use tokio::io::{Interest, Ready}; @@ -703,8 +821,8 @@ async fn clear_ready_matching_clears_ready_mut() { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No socket in miri. #[cfg(target_os = "linux")] -#[cfg_attr(miri, ignore)] async fn await_error_readiness_timestamping() { use std::net::{Ipv4Addr, SocketAddr}; @@ -760,8 +878,8 @@ fn configure_timestamping_socket(udp_socket: &std::net::UdpSocket) -> std::io::R } #[tokio::test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. #[cfg(target_os = "linux")] -#[cfg_attr(miri, ignore)] async fn await_error_readiness_invalid_address() { use std::net::{Ipv4Addr, SocketAddr}; use tokio::io::{Interest, Ready}; diff --git a/tokio/tests/io_copy_bidirectional.rs b/tokio/tests/io_copy_bidirectional.rs index 44ba6ecdf40..8aa95106dfc 100644 --- a/tokio/tests/io_copy_bidirectional.rs +++ b/tokio/tests/io_copy_bidirectional.rs @@ -1,5 +1,5 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi does not support bind() +#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi does not support bind() use std::time::Duration; use tokio::io::{self, copy_bidirectional, AsyncReadExt, AsyncWriteExt}; @@ -59,6 +59,7 @@ where } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` in miri. async fn test_basic_transfer() { symmetric(|_handle, mut a, mut b| async move { a.write_all(b"test").await.unwrap(); @@ -70,6 +71,7 @@ async fn test_basic_transfer() { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` in miri. async fn test_transfer_after_close() { symmetric(|handle, mut a, mut b| async move { AsyncWriteExt::shutdown(&mut a).await.unwrap(); @@ -89,6 +91,7 @@ async fn test_transfer_after_close() { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` in miri. async fn blocking_one_side_does_not_block_other() { symmetric(|handle, mut a, mut b| async move { block_write(&mut a).await; diff --git a/tokio/tests/io_driver.rs b/tokio/tests/io_driver.rs index 5fa1fd1da19..9f8915fb130 100644 --- a/tokio/tests/io_driver.rs +++ b/tokio/tests/io_driver.rs @@ -1,6 +1,6 @@ #![warn(rust_2018_idioms)] // Wasi does not support panic recovery or threading -#![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] +#![cfg(all(feature = "full", not(target_os = "wasi")))] use tokio::net::TcpListener; use tokio::runtime; @@ -32,6 +32,7 @@ impl Task { } #[test] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn test_drop_on_notify() { // When the reactor receives a kernel notification, it notifies the // task that holds the associated socket. If this notification results in @@ -90,6 +91,7 @@ fn test_drop_on_notify() { #[should_panic( expected = "A Tokio 1.x context was found, but IO is disabled. Call `enable_io` on the runtime builder to enable IO." )] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn panics_when_io_disabled() { let rt = runtime::Builder::new_current_thread().build().unwrap(); diff --git a/tokio/tests/io_driver_drop.rs b/tokio/tests/io_driver_drop.rs index 85c83c7ba91..0bbd379a95b 100644 --- a/tokio/tests/io_driver_drop.rs +++ b/tokio/tests/io_driver_drop.rs @@ -1,11 +1,12 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi does not support bind +#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi does not support bind use tokio::net::TcpListener; use tokio::runtime; use tokio_test::{assert_err, assert_pending, assert_ready, task}; #[test] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn tcp_doesnt_block() { let rt = rt(); @@ -25,6 +26,7 @@ fn tcp_doesnt_block() { } #[test] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn drop_wakes() { let rt = rt(); diff --git a/tokio/tests/io_read_to_string.rs b/tokio/tests/io_read_to_string.rs index f30c26caa88..80649353b28 100644 --- a/tokio/tests/io_read_to_string.rs +++ b/tokio/tests/io_read_to_string.rs @@ -23,9 +23,9 @@ async fn to_string_does_not_truncate_on_utf8_error() { let mut s = "abc".to_string(); match AsyncReadExt::read_to_string(&mut data.as_slice(), &mut s).await { - Ok(len) => panic!("Should fail: {} bytes.", len), + Ok(len) => panic!("Should fail: {len} bytes."), Err(err) if err.to_string() == "stream did not contain valid UTF-8" => {} - Err(err) => panic!("Fail: {}.", err), + Err(err) => panic!("Fail: {err}."), } assert_eq!(s, "abc"); @@ -40,9 +40,9 @@ async fn to_string_does_not_truncate_on_io_error() { let mut s = "abc".to_string(); match AsyncReadExt::read_to_string(&mut mock, &mut s).await { - Ok(len) => panic!("Should fail: {} bytes.", len), + Ok(len) => panic!("Should fail: {len} bytes."), Err(err) if err.to_string() == "whoops" => {} - Err(err) => panic!("Fail: {}.", err), + Err(err) => panic!("Fail: {err}."), } assert_eq!(s, "abc"); diff --git a/tokio/tests/macros_select.rs b/tokio/tests/macros_select.rs index fdf7fde1342..0c5ae6d9ab0 100644 --- a/tokio/tests/macros_select.rs +++ b/tokio/tests/macros_select.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![cfg(feature = "macros")] #![allow(clippy::disallowed_names)] diff --git a/tokio/tests/macros_test.rs b/tokio/tests/macros_test.rs index 2270c421e09..ce938c23514 100644 --- a/tokio/tests/macros_test.rs +++ b/tokio/tests/macros_test.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi doesn't support threading use tokio::test; diff --git a/tokio/tests/net_bind_resource.rs b/tokio/tests/net_bind_resource.rs index b29e0976255..236875515c9 100644 --- a/tokio/tests/net_bind_resource.rs +++ b/tokio/tests/net_bind_resource.rs @@ -1,5 +1,5 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi doesn't support panic recovery or bind +#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi doesn't support panic recovery or bind use tokio::net::TcpListener; @@ -7,6 +7,7 @@ use std::net; #[test] #[should_panic] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn no_runtime_panics_binding_net_tcp_listener() { let listener = net::TcpListener::bind("127.0.0.1:0").expect("failed to bind listener"); let _ = TcpListener::try_from(listener); diff --git a/tokio/tests/net_lookup_host.rs b/tokio/tests/net_lookup_host.rs index 6adce83ecbb..48b4719adc1 100644 --- a/tokio/tests/net_lookup_host.rs +++ b/tokio/tests/net_lookup_host.rs @@ -23,7 +23,7 @@ async fn lookup_str_socket_addr() { } #[tokio::test] -#[cfg_attr(miri, ignore)] +#[cfg_attr(miri, ignore)] // No `getaddrinfo` in miri. async fn resolve_dns() -> io::Result<()> { let mut hosts = net::lookup_host("localhost:3000").await?; let host = hosts.next().unwrap(); diff --git a/tokio/tests/net_panic.rs b/tokio/tests/net_panic.rs index fc1777c0e79..ef42325ef6b 100644 --- a/tokio/tests/net_panic.rs +++ b/tokio/tests/net_panic.rs @@ -1,5 +1,5 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] +#![cfg(all(feature = "full", not(target_os = "wasi")))] #![cfg(panic = "unwind")] use std::error::Error; @@ -12,6 +12,7 @@ mod support { use support::panic::test_panic; #[test] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn udp_socket_from_std_panic_caller() -> Result<(), Box> { use std::net::SocketAddr; use tokio::net::UdpSocket; @@ -34,6 +35,7 @@ fn udp_socket_from_std_panic_caller() -> Result<(), Box> { } #[test] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn tcp_listener_from_std_panic_caller() -> Result<(), Box> { let std_listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); std_listener.set_nonblocking(true).unwrap(); @@ -52,6 +54,7 @@ fn tcp_listener_from_std_panic_caller() -> Result<(), Box> { } #[test] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn tcp_stream_from_std_panic_caller() -> Result<(), Box> { let std_listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); @@ -73,6 +76,7 @@ fn tcp_stream_from_std_panic_caller() -> Result<(), Box> { #[test] #[cfg(unix)] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn unix_listener_bind_panic_caller() -> Result<(), Box> { use tokio::net::UnixListener; @@ -94,6 +98,7 @@ fn unix_listener_bind_panic_caller() -> Result<(), Box> { #[test] #[cfg(unix)] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn unix_listener_from_std_panic_caller() -> Result<(), Box> { use tokio::net::UnixListener; @@ -116,6 +121,7 @@ fn unix_listener_from_std_panic_caller() -> Result<(), Box> { #[test] #[cfg(unix)] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn unix_stream_from_std_panic_caller() -> Result<(), Box> { use tokio::net::UnixStream; @@ -139,6 +145,7 @@ fn unix_stream_from_std_panic_caller() -> Result<(), Box> { #[test] #[cfg(unix)] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn unix_datagram_from_std_panic_caller() -> Result<(), Box> { use std::os::unix::net::UnixDatagram as StdUDS; use tokio::net::UnixDatagram; diff --git a/tokio/tests/net_unix_pipe.rs b/tokio/tests/net_unix_pipe.rs index c6d2ab34203..a3a98542c56 100644 --- a/tokio/tests/net_unix_pipe.rs +++ b/tokio/tests/net_unix_pipe.rs @@ -1,6 +1,5 @@ #![cfg(feature = "full")] #![cfg(unix)] -#![cfg(not(miri))] use tokio::io::{AsyncReadExt, AsyncWriteExt, Interest}; use tokio::net::unix::pipe; @@ -38,6 +37,7 @@ impl AsRef for TempFifo { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `mkfifo` in miri. async fn fifo_simple_send() -> io::Result<()> { const DATA: &[u8] = b"this is some data to write to the fifo"; @@ -69,6 +69,7 @@ async fn fifo_simple_send() -> io::Result<()> { #[tokio::test] #[cfg(target_os = "linux")] +#[cfg_attr(miri, ignore)] // No `mkfifo` in miri. async fn fifo_simple_send_sender_first() -> io::Result<()> { const DATA: &[u8] = b"this is some data to write to the fifo"; @@ -105,6 +106,7 @@ async fn write_and_close(path: impl AsRef, msg: &[u8]) -> io::Result<()> { /// Checks EOF behavior with single reader and writers sequentially opening /// and closing a FIFO. #[tokio::test] +#[cfg_attr(miri, ignore)] // No `mkfifo` in miri. async fn fifo_multiple_writes() -> io::Result<()> { const DATA: &[u8] = b"this is some data to write to the fifo"; @@ -133,6 +135,7 @@ async fn fifo_multiple_writes() -> io::Result<()> { /// with writers sequentially opening and closing a FIFO. #[tokio::test] #[cfg(target_os = "linux")] +#[cfg_attr(miri, ignore)] // No `socket` in miri. async fn fifo_resilient_reader() -> io::Result<()> { const DATA: &[u8] = b"this is some data to write to the fifo"; @@ -163,6 +166,7 @@ async fn fifo_resilient_reader() -> io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `O_NONBLOCK` for open64 in miri. async fn open_detects_not_a_fifo() -> io::Result<()> { let dir = tempfile::Builder::new() .prefix("tokio-fifo-tests") @@ -185,6 +189,7 @@ async fn open_detects_not_a_fifo() -> io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `mkfifo` in miri. async fn from_file() -> io::Result<()> { const DATA: &[u8] = b"this is some data to write to the fifo"; @@ -221,6 +226,7 @@ async fn from_file() -> io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `fstat` in miri. async fn from_file_detects_not_a_fifo() -> io::Result<()> { let dir = tempfile::Builder::new() .prefix("tokio-fifo-tests") @@ -245,6 +251,7 @@ async fn from_file_detects_not_a_fifo() -> io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `mkfifo` in miri. async fn from_file_detects_wrong_access_mode() -> io::Result<()> { let fifo = TempFifo::new("wrong_access_mode")?; @@ -276,6 +283,7 @@ fn is_nonblocking(fd: &T) -> io::Result { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `mkfifo` in miri. async fn from_file_sets_nonblock() -> io::Result<()> { let fifo = TempFifo::new("sets_nonblock")?; @@ -303,6 +311,7 @@ fn writable_by_poll(writer: &pipe::Sender) -> bool { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `mkfifo` in miri. async fn try_read_write() -> io::Result<()> { const DATA: &[u8] = b"this is some data to write to the fifo"; @@ -343,6 +352,7 @@ async fn try_read_write() -> io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `mkfifo` in miri. async fn try_read_write_vectored() -> io::Result<()> { const DATA: &[u8] = b"this is some data to write to the fifo"; @@ -390,6 +400,7 @@ async fn try_read_write_vectored() -> io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `mkfifo` in miri. async fn try_read_buf() -> std::io::Result<()> { const DATA: &[u8] = b"this is some data to write to the fifo"; @@ -458,6 +469,7 @@ async fn anon_pipe_simple_send() -> io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. async fn anon_pipe_spawn_echo() -> std::io::Result<()> { use tokio::process::Command; @@ -488,6 +500,7 @@ async fn anon_pipe_spawn_echo() -> std::io::Result<()> { #[tokio::test] #[cfg(target_os = "linux")] +#[cfg_attr(miri, ignore)] // No `fstat` in miri. async fn anon_pipe_from_owned_fd() -> std::io::Result<()> { use nix::fcntl::OFlag; @@ -507,6 +520,7 @@ async fn anon_pipe_from_owned_fd() -> std::io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. async fn anon_pipe_into_nonblocking_fd() -> std::io::Result<()> { let (tx, rx) = pipe::pipe()?; @@ -520,6 +534,7 @@ async fn anon_pipe_into_nonblocking_fd() -> std::io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No F_GETFL for fcntl in miri. async fn anon_pipe_into_blocking_fd() -> std::io::Result<()> { let (tx, rx) = pipe::pipe()?; diff --git a/tokio/tests/no_rt.rs b/tokio/tests/no_rt.rs index 22cf17f6d4c..0730090857c 100644 --- a/tokio/tests/no_rt.rs +++ b/tokio/tests/no_rt.rs @@ -1,4 +1,4 @@ -#![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi does not support panic recovery +#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi does not support panic recovery use tokio::net::TcpStream; use tokio::sync::oneshot; @@ -20,6 +20,7 @@ fn timeout_panics_when_no_tokio_context() { #[should_panic( expected = "there is no reactor running, must be called from the context of a Tokio 1.x runtime" )] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn panics_when_no_reactor() { let srv = TcpListener::bind("127.0.0.1:0").unwrap(); let addr = srv.local_addr().unwrap(); @@ -36,6 +37,7 @@ async fn timeout_value() { #[should_panic( expected = "there is no reactor running, must be called from the context of a Tokio 1.x runtime" )] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn io_panics_when_no_tokio_context() { let _ = tokio::net::TcpListener::from_std(std::net::TcpListener::bind("127.0.0.1:0").unwrap()); } diff --git a/tokio/tests/process_issue_42.rs b/tokio/tests/process_issue_42.rs index 536feee349f..ea75f64e960 100644 --- a/tokio/tests/process_issue_42.rs +++ b/tokio/tests/process_issue_42.rs @@ -20,7 +20,7 @@ async fn issue_42() { task::spawn(async { let processes = (0..10usize).map(|i| { let mut child = Command::new("echo") - .arg(format!("I am spawned process #{}", i)) + .arg(format!("I am spawned process #{i}")) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) diff --git a/tokio/tests/rt_basic.rs b/tokio/tests/rt_basic.rs index f2ec0df9ff6..cedea3811a3 100644 --- a/tokio/tests/rt_basic.rs +++ b/tokio/tests/rt_basic.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![warn(rust_2018_idioms)] #![cfg(feature = "full")] #![cfg(not(miri))] // Possible bug on Miri. diff --git a/tokio/tests/rt_common.rs b/tokio/tests/rt_common.rs index ecf283886e2..c07e3e9ddb9 100644 --- a/tokio/tests/rt_common.rs +++ b/tokio/tests/rt_common.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![allow(clippy::needless_range_loop)] #![warn(rust_2018_idioms)] #![cfg(feature = "full")] @@ -542,6 +541,7 @@ rt_test! { } #[cfg(not(target_os="wasi"))] // Wasi does not support bind + #[cfg_attr(miri, ignore)] // No `socket` in miri. #[test] fn block_on_socket() { let rt = rt(); @@ -616,6 +616,7 @@ rt_test! { } #[cfg(not(target_os="wasi"))] // Wasi does not support bind + #[cfg_attr(miri, ignore)] // No `socket` in miri. #[test] fn socket_from_blocking() { let rt = rt(); @@ -686,6 +687,7 @@ rt_test! { // concern. There also isn't a great/obvious solution to take. For now, the // test is disabled. #[cfg(not(windows))] + #[cfg_attr(miri, ignore)] // No `socket` in miri. #[cfg(not(target_os="wasi"))] // Wasi does not support bind or threads fn io_driver_called_when_under_load() { let rt = rt(); @@ -740,6 +742,7 @@ rt_test! { /// spuriously. #[test] #[cfg(not(target_os="wasi"))] + #[cfg_attr(miri, ignore)] // No `socket` in miri. fn yield_defers_until_park() { for _ in 0..10 { if yield_defers_until_park_inner() { @@ -839,6 +842,7 @@ rt_test! { } #[cfg(not(target_os="wasi"))] // Wasi does not support threads + #[cfg_attr(miri, ignore)] // No `socket` in miri. #[test] fn client_server_block_on() { let rt = rt(); @@ -1004,6 +1008,7 @@ rt_test! { } #[cfg(not(target_os="wasi"))] // Wasi doesn't support UDP or bind() + #[cfg_attr(miri, ignore)] // No `socket` in miri. #[test] fn io_notify_while_shutting_down() { use tokio::net::UdpSocket; @@ -1135,6 +1140,7 @@ rt_test! { } #[cfg(not(target_os = "wasi"))] // Wasi does not support bind + #[cfg_attr(miri, ignore)] // No `socket` in miri. #[test] fn local_set_block_on_socket() { let rt = rt(); @@ -1157,6 +1163,7 @@ rt_test! { } #[cfg(not(target_os = "wasi"))] // Wasi does not support bind + #[cfg_attr(miri, ignore)] // No `socket` in miri. #[test] fn local_set_client_server_block_on() { let rt = rt(); diff --git a/tokio/tests/rt_handle.rs b/tokio/tests/rt_handle.rs index 9efe9b4bde9..bfbeff1b2e4 100644 --- a/tokio/tests/rt_handle.rs +++ b/tokio/tests/rt_handle.rs @@ -1,8 +1,9 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![warn(rust_2018_idioms)] #![cfg(feature = "full")] +use std::sync::Arc; use tokio::runtime::Runtime; +use tokio::sync::{mpsc, Barrier}; #[test] #[cfg_attr(panic = "abort", ignore)] @@ -65,6 +66,40 @@ fn interleave_then_enter() { let _enter = rt3.enter(); } +// If the cycle causes a leak, then miri will catch it. +#[test] +fn drop_tasks_with_reference_cycle() { + rt().block_on(async { + let (tx, mut rx) = mpsc::channel(1); + + let barrier = Arc::new(Barrier::new(3)); + let barrier_a = barrier.clone(); + let barrier_b = barrier.clone(); + + let a = tokio::spawn(async move { + let b = rx.recv().await.unwrap(); + + // Poll the JoinHandle once. This registers the waker. + // The other task cannot have finished at this point due to the barrier below. + futures::future::select(b, std::future::ready(())).await; + + barrier_a.wait().await; + }); + + let b = tokio::spawn(async move { + // Poll the JoinHandle once. This registers the waker. + // The other task cannot have finished at this point due to the barrier below. + futures::future::select(a, std::future::ready(())).await; + + barrier_b.wait().await; + }); + + tx.send(b).await.unwrap(); + + barrier.wait().await; + }); +} + #[cfg(tokio_unstable)] mod unstable { use super::*; diff --git a/tokio/tests/rt_handle_block_on.rs b/tokio/tests/rt_handle_block_on.rs index 81656bd8140..4037fb93c45 100644 --- a/tokio/tests/rt_handle_block_on.rs +++ b/tokio/tests/rt_handle_block_on.rs @@ -212,6 +212,7 @@ rt_test! { // ==== net ====== #[test] + #[cfg_attr(miri, ignore)] // No `socket` in miri. fn tcp_listener_bind() { let rt = rt(); let _enter = rt.enter(); @@ -262,6 +263,7 @@ rt_test! { } #[test] + #[cfg_attr(miri, ignore)] // No `socket` in miri. fn udp_socket_bind() { let rt = rt(); let _enter = rt.enter(); @@ -422,6 +424,7 @@ rt_test! { #[cfg(not(target_os = "wasi"))] multi_threaded_rt_test! { #[cfg(unix)] + #[cfg_attr(miri, ignore)] // No `socket` in miri. #[test] fn unix_listener_bind() { let rt = rt(); diff --git a/tokio/tests/rt_local.rs b/tokio/tests/rt_local.rs index 1f14f5444d3..5d276250b34 100644 --- a/tokio/tests/rt_local.rs +++ b/tokio/tests/rt_local.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", tokio_unstable))] diff --git a/tokio/tests/rt_metrics.rs b/tokio/tests/rt_metrics.rs index e9f351007d5..ad3b0e367e0 100644 --- a/tokio/tests/rt_metrics.rs +++ b/tokio/tests/rt_metrics.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", not(target_os = "wasi"), target_has_atomic = "64"))] diff --git a/tokio/tests/rt_threaded.rs b/tokio/tests/rt_threaded.rs index b32ab3195ae..f0ed8443f9c 100644 --- a/tokio/tests/rt_threaded.rs +++ b/tokio/tests/rt_threaded.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![warn(rust_2018_idioms)] // Too slow on miri. #![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] @@ -189,6 +188,7 @@ fn lifo_slot_budget() { } #[test] +#[cfg_attr(miri, ignore)] // No `socket` in miri. fn spawn_shutdown() { let rt = rt(); let (tx, rx) = mpsc::channel(); diff --git a/tokio/tests/rt_threaded_alt.rs b/tokio/tests/rt_threaded_alt.rs index f7e52af83dd..c1dc71dedc1 100644 --- a/tokio/tests/rt_threaded_alt.rs +++ b/tokio/tests/rt_threaded_alt.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", not(target_os = "wasi")))] #![cfg(tokio_unstable)] diff --git a/tokio/tests/rt_unstable_metrics.rs b/tokio/tests/rt_unstable_metrics.rs index df05bfbf7ce..60cdc525ff1 100644 --- a/tokio/tests/rt_unstable_metrics.rs +++ b/tokio/tests/rt_unstable_metrics.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![warn(rust_2018_idioms)] #![cfg(all( feature = "full", @@ -424,6 +423,34 @@ fn log_histogram() { assert_eq!(N, n); } +#[test] +fn minimal_log_histogram() { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .enable_metrics_poll_time_histogram() + .metrics_poll_time_histogram_configuration(HistogramConfiguration::log( + LogHistogram::builder() + .max_value(Duration::from_millis(4)) + .min_value(Duration::from_micros(20)) + .precision_exact(0), + )) + .build() + .unwrap(); + let metrics = rt.metrics(); + let num_buckets = rt.metrics().poll_time_histogram_num_buckets(); + for b in 1..num_buckets - 1 { + let range = metrics.poll_time_histogram_bucket_range(b); + let size = range.end - range.start; + // Assert the buckets continue doubling in size + assert_eq!( + size, + Duration::from_nanos((1 << (b - 1)) * 16384), + "incorrect range for {b}" + ); + } + assert_eq!(num_buckets, 10); +} + #[test] #[allow(deprecated)] fn legacy_log_histogram() { diff --git a/tokio/tests/signal_ctrl_c.rs b/tokio/tests/signal_ctrl_c.rs index ebeee4d9e2e..1541436e9d8 100644 --- a/tokio/tests/signal_ctrl_c.rs +++ b/tokio/tests/signal_ctrl_c.rs @@ -9,23 +9,15 @@ mod support { use support::signal::send_signal; use tokio::signal; -use tokio::sync::oneshot; use tokio_test::assert_ok; #[tokio::test] async fn ctrl_c() { let ctrl_c = signal::ctrl_c(); - let (fire, wait) = oneshot::channel(); - - // NB: simulate a signal coming in by exercising our signal handler - // to avoid complications with sending SIGINT to the test process tokio::spawn(async { - wait.await.expect("wait failed"); send_signal(libc::SIGINT); }); - let _ = fire.send(()); - assert_ok!(ctrl_c.await); } diff --git a/tokio/tests/signal_drop_recv.rs b/tokio/tests/signal_drop_recv.rs index f1da899b040..40c76520321 100644 --- a/tokio/tests/signal_drop_recv.rs +++ b/tokio/tests/signal_drop_recv.rs @@ -1,7 +1,7 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "full")] #![cfg(unix)] -#![cfg(not(miri))] +#![cfg(not(miri))] // No `sigaction` in Miri. mod support { pub mod signal; diff --git a/tokio/tests/signal_drop_rt.rs b/tokio/tests/signal_drop_rt.rs index 24fd72865c0..3c909b2313d 100644 --- a/tokio/tests/signal_drop_rt.rs +++ b/tokio/tests/signal_drop_rt.rs @@ -1,7 +1,7 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "full")] #![cfg(unix)] -#![cfg(not(miri))] +#![cfg(not(miri))] // No `sigaction` in miri. mod support { pub mod signal; diff --git a/tokio/tests/signal_drop_signal.rs b/tokio/tests/signal_drop_signal.rs index 9eedd3f467e..6b4a3b63dd8 100644 --- a/tokio/tests/signal_drop_signal.rs +++ b/tokio/tests/signal_drop_signal.rs @@ -1,7 +1,7 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "full")] #![cfg(unix)] -#![cfg(not(miri))] +#![cfg(not(miri))] // No `sigaction` in miri. mod support { pub mod signal; diff --git a/tokio/tests/signal_info.rs b/tokio/tests/signal_info.rs new file mode 100644 index 00000000000..4b1bcd53c05 --- /dev/null +++ b/tokio/tests/signal_info.rs @@ -0,0 +1,35 @@ +#![warn(rust_2018_idioms)] +#![cfg(feature = "full")] +#![cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "illumos" +))] +#![cfg(not(miri))] // No `sigaction` on Miri + +mod support { + pub mod signal; +} +use support::signal::send_signal; + +use tokio::signal; +use tokio::signal::unix::SignalKind; +use tokio::time::{timeout, Duration}; + +#[tokio::test] +async fn siginfo() { + let mut sig = signal::unix::signal(SignalKind::info()).expect("installed signal handler"); + + tokio::spawn(async { + send_signal(libc::SIGINFO); + }); + + // Add a timeout to ensure the test doesn't hang. + timeout(Duration::from_secs(5), sig.recv()) + .await + .expect("received SIGINFO signal in time") + .expect("received SIGINFO signal"); +} diff --git a/tokio/tests/signal_realtime.rs b/tokio/tests/signal_realtime.rs new file mode 100644 index 00000000000..efaa4843d2f --- /dev/null +++ b/tokio/tests/signal_realtime.rs @@ -0,0 +1,105 @@ +#![warn(rust_2018_idioms)] +#![cfg(feature = "full")] +#![cfg(any(target_os = "linux", target_os = "illumos"))] +#![cfg(not(miri))] // No `sigaction` in Miri. + +mod support { + pub mod signal; +} + +use libc::c_int; +use support::signal::send_signal; + +use futures::stream::{FuturesUnordered, StreamExt}; +use std::collections::HashMap; +use tokio::signal::unix::{signal, SignalKind}; +use tokio::time::{sleep, Duration}; +use tokio_test::assert_ok; + +#[tokio::test] +async fn signal_realtime() { + // Attempt to register a real-time signal for everything between SIGRTMIN + // and SIGRTMAX. + let signals = (libc::SIGRTMIN()..=sigrt_max()) + .map(|signum| { + let sig = assert_ok!( + signal(SignalKind::from_raw(signum)), + "failed to create signal for {}", + sigrtnum_to_string(signum), + ); + (signum, sig) + }) + .collect::>(); + + eprintln!( + "registered {} signals in the range {}..={}", + signals.len(), + libc::SIGRTMIN(), + libc::SIGRTMAX() + ); + + // Now send signals to each of the registered signals. + for signum in libc::SIGRTMIN()..=sigrt_max() { + send_signal(signum); + } + + let futures = signals + .into_iter() + .map(|(signum, mut sig)| async move { + let res = sig.recv().await; + (signum, res) + }) + .collect::>(); + + // Ensure that all signals are received in time -- attempt to get whatever + // we can. + let sleep = std::pin::pin!(sleep(Duration::from_secs(5))); + let done = futures.take_until(sleep).collect::>().await; + + let mut none = Vec::new(); + let mut missing = Vec::new(); + for signum in libc::SIGRTMIN()..=sigrt_max() { + match done.get(&signum) { + Some(Some(())) => {} + Some(None) => none.push(signum), + None => missing.push(signum), + } + } + + if none.is_empty() && missing.is_empty() { + return; + } + + let mut msg = String::new(); + if !none.is_empty() { + msg.push_str("no signals received for:\n"); + for signum in none { + msg.push_str(&format!("- {}\n", sigrtnum_to_string(signum))); + } + } + + if !missing.is_empty() { + msg.push_str("missing signals for:\n"); + for signum in missing { + msg.push_str(&format!("- {}\n", sigrtnum_to_string(signum))); + } + } + + panic!("{}", msg); +} + +fn sigrt_max() -> c_int { + // Generally, you would expect this to be SIGRTMAX. But QEMU only supports + // 28 real-time signals even though it might report SIGRTMAX to be higher. + // See https://wiki.qemu.org/ChangeLog/9.2#signals. + // + // The goal of this test is to test that real-time signals are supported in + // general, not necessarily that every single signal is supported (which, as + // this example suggests, depends on the execution environment). So cap this + // at SIGRTMIN+27 (i.e. SIGRTMIN..=SIGRTMIN+27, so 28 signals inclusive). + libc::SIGRTMAX().min(libc::SIGRTMIN() + 27) +} + +fn sigrtnum_to_string(signum: i32) -> String { + format!("SIGRTMIN+{} (signal {})", signum - libc::SIGRTMIN(), signum) +} diff --git a/tokio/tests/support/signal.rs b/tokio/tests/support/signal.rs index ea06058764d..c769a42dbd3 100644 --- a/tokio/tests/support/signal.rs +++ b/tokio/tests/support/signal.rs @@ -2,6 +2,14 @@ pub fn send_signal(signal: libc::c_int) { use libc::{getpid, kill}; unsafe { - assert_eq!(kill(getpid(), signal), 0); + let pid = getpid(); + assert_eq!( + kill(pid, signal), + 0, + "kill(pid = {}, {}) failed with error: {}", + pid, + signal, + std::io::Error::last_os_error(), + ); } } diff --git a/tokio/tests/sync_broadcast.rs b/tokio/tests/sync_broadcast.rs index 3af96bdb5d5..2153555694b 100644 --- a/tokio/tests/sync_broadcast.rs +++ b/tokio/tests/sync_broadcast.rs @@ -656,3 +656,54 @@ async fn receiver_recv_is_cooperative() { _ = tokio::task::yield_now() => {}, } } + +#[test] +fn broadcast_sender_closed() { + let (tx, rx) = broadcast::channel::<()>(1); + let rx2 = tx.subscribe(); + + let mut task = task::spawn(tx.closed()); + assert_pending!(task.poll()); + + drop(rx); + assert!(!task.is_woken()); + assert_pending!(task.poll()); + + drop(rx2); + assert!(task.is_woken()); + assert_ready!(task.poll()); +} + +#[test] +fn broadcast_sender_closed_with_extra_subscribe() { + let (tx, rx) = broadcast::channel::<()>(1); + let rx2 = tx.subscribe(); + + let mut task = task::spawn(tx.closed()); + assert_pending!(task.poll()); + + drop(rx); + assert!(!task.is_woken()); + assert_pending!(task.poll()); + + drop(rx2); + assert!(task.is_woken()); + + let rx3 = tx.subscribe(); + assert_pending!(task.poll()); + + drop(rx3); + assert!(task.is_woken()); + assert_ready!(task.poll()); + + let mut task2 = task::spawn(tx.closed()); + assert_ready!(task2.poll()); + + let rx4 = tx.subscribe(); + let mut task3 = task::spawn(tx.closed()); + assert_pending!(task3.poll()); + + drop(rx4); + assert!(task3.is_woken()); + assert_ready!(task3.poll()); +} diff --git a/tokio/tests/sync_mpsc.rs b/tokio/tests/sync_mpsc.rs index 638ced588ce..577e9c35faa 100644 --- a/tokio/tests/sync_mpsc.rs +++ b/tokio/tests/sync_mpsc.rs @@ -1454,4 +1454,50 @@ async fn test_is_empty_32_msgs() { } } +#[test] +#[cfg(not(panic = "abort"))] +fn drop_all_elements_during_panic() { + use std::sync::atomic::AtomicUsize; + use std::sync::atomic::Ordering::Relaxed; + use tokio::sync::mpsc::UnboundedReceiver; + use tokio::sync::mpsc::UnboundedSender; + + static COUNTER: AtomicUsize = AtomicUsize::new(0); + + struct A(bool); + impl Drop for A { + // cause a panic when inner value is `true`. + fn drop(&mut self) { + COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + if self.0 { + panic!("panic!") + } + } + } + + fn func(tx: UnboundedSender, rx: UnboundedReceiver) { + tx.send(A(true)).unwrap(); + tx.send(A(false)).unwrap(); + tx.send(A(false)).unwrap(); + + drop(rx); + + // `mpsc::Rx`'s drop is called and gets panicked while dropping the first value, + // but will keep dropping following elements. + } + + let (tx, rx) = mpsc::unbounded_channel(); + + let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| { + func(tx.clone(), rx); + })); + + // all A's destructor should be called at this point, even before `mpsc::Chan`'s + // drop gets called. + assert_eq!(COUNTER.load(Relaxed), 3); + + drop(tx); + // `mpsc::Chan`'s drop is called, freeing the `Block` memory allocation. +} + fn is_debug(_: &T) {} diff --git a/tokio/tests/sync_mutex.rs b/tokio/tests/sync_mutex.rs index f423c82e7b1..8d74addad75 100644 --- a/tokio/tests/sync_mutex.rs +++ b/tokio/tests/sync_mutex.rs @@ -165,14 +165,14 @@ fn try_lock() { async fn debug_format() { let s = "debug"; let m = Mutex::new(s.to_string()); - assert_eq!(format!("{:?}", s), format!("{:?}", m.lock().await)); + assert_eq!(format!("{s:?}"), format!("{:?}", m.lock().await)); } #[maybe_tokio_test] async fn mutex_debug() { let s = "data"; let m = Mutex::new(s.to_string()); - assert_eq!(format!("{:?}", m), r#"Mutex { data: "data" }"#); + assert_eq!(format!("{m:?}"), r#"Mutex { data: "data" }"#); let _guard = m.lock().await; - assert_eq!(format!("{:?}", m), r#"Mutex { data: }"#) + assert_eq!(format!("{m:?}"), r#"Mutex { data: }"#) } diff --git a/tokio/tests/sync_mutex_owned.rs b/tokio/tests/sync_mutex_owned.rs index d890c5327f1..28b2afbf32b 100644 --- a/tokio/tests/sync_mutex_owned.rs +++ b/tokio/tests/sync_mutex_owned.rs @@ -133,5 +133,5 @@ fn try_lock_owned() { async fn debug_format() { let s = "debug"; let m = Arc::new(Mutex::new(s.to_string())); - assert_eq!(format!("{:?}", s), format!("{:?}", m.lock_owned().await)); + assert_eq!(format!("{s:?}"), format!("{:?}", m.lock_owned().await)); } diff --git a/tokio/tests/task_abort.rs b/tokio/tests/task_abort.rs index 0e742de13e2..8de366454e0 100644 --- a/tokio/tests/task_abort.rs +++ b/tokio/tests/task_abort.rs @@ -233,7 +233,7 @@ fn test_join_error_display() { // `String` payload let join_err = tokio::spawn(async move { let value = 1234; - panic!("Format-args payload: {}", value) + panic!("Format-args payload: {value}") }) .await .unwrap_err(); @@ -244,8 +244,7 @@ fn test_join_error_display() { assert!( join_err_str.starts_with("task ") && join_err_str.ends_with(" panicked with message \"Format-args payload: 1234\""), - "Unexpected join_err_str {:?}", - join_err_str + "Unexpected join_err_str {join_err_str:?}" ); // `&'static str` payload @@ -258,8 +257,7 @@ fn test_join_error_display() { assert!( join_err_str.starts_with("task ") && join_err_str.ends_with(" panicked with message \"Const payload\""), - "Unexpected join_err_str {:?}", - join_err_str + "Unexpected join_err_str {join_err_str:?}" ); // Non-string payload @@ -271,8 +269,7 @@ fn test_join_error_display() { assert!( join_err_str.starts_with("task ") && join_err_str.ends_with(" panicked"), - "Unexpected join_err_str {:?}", - join_err_str + "Unexpected join_err_str {join_err_str:?}" ); }); } @@ -287,19 +284,18 @@ fn test_join_error_debug() { // `String` payload let join_err = tokio::spawn(async move { let value = 1234; - panic!("Format-args payload: {}", value) + panic!("Format-args payload: {value}") }) .await .unwrap_err(); // We can't assert the full output because the task ID can change. - let join_err_str = format!("{:?}", join_err); + let join_err_str = format!("{join_err:?}"); assert!( join_err_str.starts_with("JoinError::Panic(Id(") && join_err_str.ends_with("), \"Format-args payload: 1234\", ...)"), - "Unexpected join_err_str {:?}", - join_err_str + "Unexpected join_err_str {join_err_str:?}" ); // `&'static str` payload @@ -307,13 +303,12 @@ fn test_join_error_debug() { .await .unwrap_err(); - let join_err_str = format!("{:?}", join_err); + let join_err_str = format!("{join_err:?}"); assert!( join_err_str.starts_with("JoinError::Panic(Id(") && join_err_str.ends_with("), \"Const payload\", ...)"), - "Unexpected join_err_str {:?}", - join_err_str + "Unexpected join_err_str {join_err_str:?}" ); // Non-string payload @@ -321,12 +316,11 @@ fn test_join_error_debug() { .await .unwrap_err(); - let join_err_str = format!("{:?}", join_err); + let join_err_str = format!("{join_err:?}"); assert!( join_err_str.starts_with("JoinError::Panic(Id(") && join_err_str.ends_with("), ...)"), - "Unexpected join_err_str {:?}", - join_err_str + "Unexpected join_err_str {join_err_str:?}" ); }); } diff --git a/tokio/tests/task_blocking.rs b/tokio/tests/task_blocking.rs index b0e6e62ef70..d22176a6708 100644 --- a/tokio/tests/task_blocking.rs +++ b/tokio/tests/task_blocking.rs @@ -134,8 +134,7 @@ fn useful_panic_message_when_dropping_rt_in_rt() { assert!( err.contains("Cannot drop a runtime"), - "Wrong panic message: {:?}", - err + "Wrong panic message: {err:?}" ); } diff --git a/tokio/tests/task_builder.rs b/tokio/tests/task_builder.rs index 4d1248500ab..c700f229f9f 100644 --- a/tokio/tests/task_builder.rs +++ b/tokio/tests/task_builder.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![cfg(all(tokio_unstable, feature = "tracing"))] use std::rc::Rc; diff --git a/tokio/tests/task_hooks.rs b/tokio/tests/task_hooks.rs index 1e2de7e4b4c..185b9126cca 100644 --- a/tokio/tests/task_hooks.rs +++ b/tokio/tests/task_hooks.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", tokio_unstable, target_has_atomic = "64"))] diff --git a/tokio/tests/task_id.rs b/tokio/tests/task_id.rs index c0aed66f16f..0cbf80d5ace 100644 --- a/tokio/tests/task_id.rs +++ b/tokio/tests/task_id.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![warn(rust_2018_idioms)] #![cfg(feature = "full")] diff --git a/tokio/tests/task_join_set.rs b/tokio/tests/task_join_set.rs index da0652627dc..f705fa507d7 100644 --- a/tokio/tests/task_join_set.rs +++ b/tokio/tests/task_join_set.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![warn(rust_2018_idioms)] #![cfg(feature = "full")] @@ -254,7 +253,7 @@ async fn join_set_coop() { loop { match set.join_next().now_or_never() { Some(Some(Ok(()))) => {} - Some(Some(Err(err))) => panic!("failed: {}", err), + Some(Some(Err(err))) => panic!("failed: {err}"), None => { coop_count += 1; tokio::task::yield_now().await; @@ -294,7 +293,7 @@ async fn try_join_next() { Some(Ok(())) => { count += 1; } - Some(Err(err)) => panic!("failed: {}", err), + Some(Err(err)) => panic!("failed: {err}"), None => { break; } diff --git a/tokio/tests/task_local_set.rs b/tokio/tests/task_local_set.rs index ac46291a36c..30f20ed0d1d 100644 --- a/tokio/tests/task_local_set.rs +++ b/tokio/tests/task_local_set.rs @@ -1,4 +1,3 @@ -#![allow(unknown_lints, unexpected_cfgs)] #![warn(rust_2018_idioms)] #![cfg(feature = "full")] @@ -385,9 +384,8 @@ fn with_timeout(timeout: Duration, f: impl FnOnce() + Send + 'static) { // in case CI is slow, we'll give it a long timeout. match done_rx.recv_timeout(timeout) { Err(RecvTimeoutError::Timeout) => panic!( - "test did not complete within {:?} seconds, \ + "test did not complete within {timeout:?} seconds, \ we have (probably) entered an infinite loop!", - timeout, ), // Did the test thread panic? We'll find out for sure when we `join` // with it. diff --git a/tokio/tests/task_trace_self.rs b/tokio/tests/task_trace_self.rs new file mode 100644 index 00000000000..a4dc1f37e9c --- /dev/null +++ b/tokio/tests/task_trace_self.rs @@ -0,0 +1,107 @@ +#![allow(unknown_lints, unexpected_cfgs)] +#![cfg(all( + tokio_unstable, + tokio_taskdump, + target_os = "linux", + any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") +))] + +use std::future::Future; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; +use std::time::{Duration, Instant}; + +use tokio::runtime::dump::{Root, Trace}; + +pin_project_lite::pin_project! { + pub struct PrettyFuture { + #[pin] + f: Root, + t_last: State, + logs: Arc>>, + } +} + +enum State { + NotStarted, + Running { since: Instant }, + Alerted, +} + +impl PrettyFuture { + pub fn pretty(f: F, logs: Arc>>) -> Self { + PrettyFuture { + f: Trace::root(f), + t_last: State::NotStarted, + logs, + } + } +} + +impl Future for PrettyFuture { + type Output = F::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + let now = Instant::now(); + let t_last = match this.t_last { + State::Running { since } => Some(*since), + State::NotStarted => { + *this.t_last = State::Running { since: now }; + None + } + State::Alerted => { + // don't double-alert for the same future + None + } + }; + if t_last.is_some_and(|t_last| now.duration_since(t_last) > Duration::from_millis(500)) { + let (res, trace) = tokio::runtime::dump::Trace::capture(|| this.f.as_mut().poll(cx)); + this.logs.lock().unwrap().push(trace); + *this.t_last = State::Alerted; + return res; + } + this.f.poll(cx) + } +} + +#[tokio::test] +async fn task_trace_self() { + let log = Arc::new(Mutex::new(vec![])); + let log2 = Arc::new(Mutex::new(vec![])); + let mut good_line = vec![]; + let mut bad_line = vec![]; + PrettyFuture::pretty( + PrettyFuture::pretty( + async { + bad_line.push(line!() + 1); + tokio::task::yield_now().await; + bad_line.push(line!() + 1); + tokio::time::sleep(Duration::from_millis(1)).await; + for _ in 0..100 { + good_line.push(line!() + 1); + tokio::time::sleep(Duration::from_millis(10)).await; + } + }, + log.clone(), + ), + log2.clone(), + ) + .await; + for line in good_line { + let s = format!("{}:{}:", file!(), line); + assert!(log.lock().unwrap().iter().any(|x| { + eprintln!("{}", x); + format!("{}", x).contains(&s) + })); + } + for line in bad_line { + let s = format!("{}:{}:", file!(), line); + assert!(!log + .lock() + .unwrap() + .iter() + .any(|x| format!("{}", x).contains(&s))); + } +} diff --git a/tokio/tests/task_yield_now.rs b/tokio/tests/task_yield_now.rs index 3cb8cb16e70..e6fe5d2009a 100644 --- a/tokio/tests/task_yield_now.rs +++ b/tokio/tests/task_yield_now.rs @@ -1,5 +1,4 @@ -#![allow(unknown_lints, unexpected_cfgs)] -#![cfg(all(feature = "full", tokio_unstable))] +#![cfg(all(feature = "full", not(target_os = "wasi"), tokio_unstable))] use tokio::task; use tokio_test::task::spawn; @@ -15,3 +14,11 @@ fn yield_now_outside_of_runtime() { assert!(task.is_woken()); assert!(task.poll().is_ready()); } + +#[tokio::test(flavor = "multi_thread")] +async fn yield_now_external_executor_and_block_in_place() { + let j = tokio::spawn(async { + task::block_in_place(|| futures::executor::block_on(task::yield_now())); + }); + j.await.unwrap(); +} diff --git a/tokio/tests/tcp_accept.rs b/tokio/tests/tcp_accept.rs index 99f5f5c2b22..766304778df 100644 --- a/tokio/tests/tcp_accept.rs +++ b/tokio/tests/tcp_accept.rs @@ -1,5 +1,6 @@ #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi doesn't support bind + // No `socket` on miri. use tokio::net::{TcpListener, TcpStream}; use tokio::sync::{mpsc, oneshot}; diff --git a/tokio/tests/tcp_connect.rs b/tokio/tests/tcp_connect.rs index 2e9cf722948..feaf703eded 100644 --- a/tokio/tests/tcp_connect.rs +++ b/tokio/tests/tcp_connect.rs @@ -1,5 +1,6 @@ #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi doesn't support bind + // No `socket` on miri. use tokio::net::{TcpListener, TcpStream}; use tokio::sync::oneshot; diff --git a/tokio/tests/tcp_echo.rs b/tokio/tests/tcp_echo.rs index 2ec1a134806..fcec280fbad 100644 --- a/tokio/tests/tcp_echo.rs +++ b/tokio/tests/tcp_echo.rs @@ -1,5 +1,6 @@ #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi doesn't support bind + // No socket on miri. use tokio::io::{self, AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; diff --git a/tokio/tests/tcp_into_split.rs b/tokio/tests/tcp_into_split.rs index 34f35ca9241..7fa0d0cb2b7 100644 --- a/tokio/tests/tcp_into_split.rs +++ b/tokio/tests/tcp_into_split.rs @@ -1,5 +1,6 @@ #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi doesn't support bind + // No `socket` on miri. use std::io::{Error, ErrorKind, Result}; use std::io::{Read, Write}; @@ -97,7 +98,7 @@ async fn drop_write() -> Result<()> { Ok(0) => Ok(()), Ok(len) => Err(Error::new( ErrorKind::Other, - format!("Unexpected read: {} bytes.", len), + format!("Unexpected read: {len} bytes."), )), Err(err) => Err(err), }; @@ -122,8 +123,8 @@ async fn drop_write() -> Result<()> { match read_half.read(&mut read_buf[..]).await { Ok(0) => {} - Ok(len) => panic!("Unexpected read: {} bytes.", len), - Err(err) => panic!("Unexpected error: {}.", err), + Ok(len) => panic!("Unexpected read: {len} bytes."), + Err(err) => panic!("Unexpected error: {err}."), } handle.join().unwrap().unwrap(); diff --git a/tokio/tests/tcp_into_std.rs b/tokio/tests/tcp_into_std.rs index 95a61525dd1..66d1a9cc612 100644 --- a/tokio/tests/tcp_into_std.rs +++ b/tokio/tests/tcp_into_std.rs @@ -1,5 +1,6 @@ #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi doesn't support bind + // No socket on `miri`. use std::io::Read; use std::io::Result; diff --git a/tokio/tests/tcp_peek.rs b/tokio/tests/tcp_peek.rs index 2ebcb418803..e633badb699 100644 --- a/tokio/tests/tcp_peek.rs +++ b/tokio/tests/tcp_peek.rs @@ -1,5 +1,6 @@ #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi doesn't support bind + // No `socket` on miri. use tokio::io::AsyncReadExt; use tokio::net::TcpStream; diff --git a/tokio/tests/tcp_shutdown.rs b/tokio/tests/tcp_shutdown.rs index d5a6488e783..2497c1a401d 100644 --- a/tokio/tests/tcp_shutdown.rs +++ b/tokio/tests/tcp_shutdown.rs @@ -1,5 +1,6 @@ #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi doesn't support bind + // No `socket` on miri. use tokio::io::{self, AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream}; diff --git a/tokio/tests/tcp_socket.rs b/tokio/tests/tcp_socket.rs index 0abebb6ccb2..c05dc1f5319 100644 --- a/tokio/tests/tcp_socket.rs +++ b/tokio/tests/tcp_socket.rs @@ -1,5 +1,6 @@ #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi doesn't support bind + // No `socket` on miri. use std::time::Duration; use tokio::net::TcpSocket; diff --git a/tokio/tests/tcp_split.rs b/tokio/tests/tcp_split.rs index 4cbec846cfa..1b0b8dedc4d 100644 --- a/tokio/tests/tcp_split.rs +++ b/tokio/tests/tcp_split.rs @@ -1,5 +1,6 @@ #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi doesn't support bind + // No `socket` on miri. use std::io::Result; use std::io::{Read, Write}; diff --git a/tokio/tests/tcp_stream.rs b/tokio/tests/tcp_stream.rs index fd89ebeabd3..1352f534405 100644 --- a/tokio/tests/tcp_stream.rs +++ b/tokio/tests/tcp_stream.rs @@ -1,5 +1,5 @@ #![warn(rust_2018_idioms)] -#![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi doesn't support bind +#![cfg(all(feature = "full", not(target_os = "wasi")))] // Wasi doesn't support bind use tokio::io::{AsyncReadExt, AsyncWriteExt, Interest}; use tokio::net::{TcpListener, TcpStream}; @@ -13,6 +13,7 @@ use std::task::Poll; use std::time::Duration; #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn set_linger() { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); @@ -28,6 +29,7 @@ async fn set_linger() { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn try_read_write() { const DATA: &[u8] = b"this is some data to write to the socket"; @@ -65,7 +67,7 @@ async fn try_read_write() { Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { break; } - Err(e) => panic!("error = {:?}", e), + Err(e) => panic!("error = {e:?}"), } } @@ -84,7 +86,7 @@ async fn try_read_write() { match server.try_read(&mut read[i..]) { Ok(n) => i += n, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("error = {:?}", e), + Err(e) => panic!("error = {e:?}"), } } @@ -106,7 +108,7 @@ async fn try_read_write() { Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { break; } - Err(e) => panic!("error = {:?}", e), + Err(e) => panic!("error = {e:?}"), } } @@ -129,7 +131,7 @@ async fn try_read_write() { match server.try_read_vectored(&mut bufs) { Ok(n) => i += n, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("error = {:?}", e), + Err(e) => panic!("error = {e:?}"), } } @@ -208,6 +210,7 @@ macro_rules! assert_not_writable_by_polling { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn poll_read_ready() { let (mut client, mut server) = create_pair().await; @@ -231,6 +234,7 @@ async fn poll_read_ready() { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn poll_write_ready() { let (mut client, server) = create_pair().await; @@ -284,6 +288,7 @@ fn write_until_pending(stream: &mut TcpStream) -> usize { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn try_read_buf() { const DATA: &[u8] = b"this is some data to write to the socket"; @@ -321,7 +326,7 @@ async fn try_read_buf() { Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { break; } - Err(e) => panic!("error = {:?}", e), + Err(e) => panic!("error = {e:?}"), } } @@ -340,7 +345,7 @@ async fn try_read_buf() { match server.try_read_buf(&mut read) { Ok(n) => i += n, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("error = {:?}", e), + Err(e) => panic!("error = {e:?}"), } } @@ -363,6 +368,7 @@ async fn try_read_buf() { // read_closed is a best effort event, so test only for no false positives. #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn read_closed() { let (client, mut server) = create_pair().await; @@ -378,6 +384,7 @@ async fn read_closed() { // write_closed is a best effort event, so test only for no false positives. #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn write_closed() { let (mut client, mut server) = create_pair().await; diff --git a/tokio/tests/tracing-instrumentation/Cargo.toml b/tokio/tests/tracing-instrumentation/Cargo.toml deleted file mode 100644 index 94c88dc2810..00000000000 --- a/tokio/tests/tracing-instrumentation/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "tracing-instrumentation" -version = "0.1.0" -edition = "2021" - -[dependencies] - -[dev-dependencies] -futures = { version = "0.3.0", features = ["async-await"] } -tokio = { version = "1.33.0", path = "../..", features = ["full", "tracing"] } -tracing = { version = "0.1.40", git = "https://github.com/tokio-rs/tracing.git", tag = "tracing-0.1.40" } -tracing-mock = { version = "0.1.0", git = "https://github.com/tokio-rs/tracing.git", tag = "tracing-0.1.40" } - -[patch.crates-io] -tracing = { git = "https://github.com/tokio-rs/tracing.git", tag = "tracing-0.1.40" } - -[workspace] diff --git a/tokio/tests/tracing-instrumentation/tests/sync.rs b/tokio/tests/tracing_sync.rs similarity index 75% rename from tokio/tests/tracing-instrumentation/tests/sync.rs rename to tokio/tests/tracing_sync.rs index 110928dda78..7065282c44b 100644 --- a/tokio/tests/tracing-instrumentation/tests/sync.rs +++ b/tokio/tests/tracing_sync.rs @@ -2,6 +2,8 @@ //! //! These tests ensure that the instrumentation for tokio //! synchronization primitives is correct. +#![warn(rust_2018_idioms)] +#![cfg(all(tokio_unstable, feature = "tracing", target_has_atomic = "64"))] use tokio::sync; use tracing_mock::{expect, subscriber}; @@ -14,19 +16,23 @@ async fn test_barrier_creates_span() { let size_event = expect::event() .with_target("runtime::resource::state_update") - .with_fields(expect::field("size").with_value(&1u64)); + .with_fields(expect::field("size").with_value(&1_u64)); let arrived_event = expect::event() .with_target("runtime::resource::state_update") - .with_fields(expect::field("arrived").with_value(&0)); + .with_fields(expect::field("arrived").with_value(&0_i64)); let (subscriber, handle) = subscriber::mock() - .new_span(barrier_span.clone().with_explicit_parent(None)) - .enter(barrier_span.clone()) + .new_span( + barrier_span + .clone() + .with_ancestry(expect::is_explicit_root()), + ) + .enter(&barrier_span) .event(size_event) .event(arrived_event) - .exit(barrier_span.clone()) - .drop_span(barrier_span) + .exit(&barrier_span) + .drop_span(&barrier_span) .run_with_handle(); { @@ -57,16 +63,20 @@ async fn test_mutex_creates_span() { .with_fields(expect::field("permits.op").with_value(&"override")); let (subscriber, handle) = subscriber::mock() - .new_span(mutex_span.clone().with_explicit_parent(None)) - .enter(mutex_span.clone()) + .new_span(mutex_span.clone().with_ancestry(expect::is_explicit_root())) + .enter(&mutex_span) .event(locked_event) - .new_span(batch_semaphore_span.clone().with_explicit_parent(None)) - .enter(batch_semaphore_span.clone()) + .new_span( + batch_semaphore_span + .clone() + .with_ancestry(expect::is_explicit_root()), + ) + .enter(&batch_semaphore_span) .event(batch_semaphore_permits_event) - .exit(batch_semaphore_span.clone()) - .exit(mutex_span.clone()) - .drop_span(mutex_span) - .drop_span(batch_semaphore_span) + .exit(&batch_semaphore_span) + .exit(&mutex_span) + .drop_span(&mutex_span) + .drop_span(&batch_semaphore_span) .run_with_handle(); { @@ -79,7 +89,9 @@ async fn test_mutex_creates_span() { #[tokio::test] async fn test_oneshot_creates_span() { + let oneshot_span_id = expect::id(); let oneshot_span = expect::span() + .with_id(oneshot_span_id.clone()) .named("runtime.resource") .with_target("tokio::sync::oneshot"); @@ -113,7 +125,9 @@ async fn test_oneshot_creates_span() { .with_fields(expect::field("value_received").with_value(&false)) .with_fields(expect::field("value_received.op").with_value(&"override")); + let async_op_span_id = expect::id(); let async_op_span = expect::span() + .with_id(async_op_span_id.clone()) .named("runtime.resource.async_op") .with_target("tokio::sync::oneshot"); @@ -122,34 +136,46 @@ async fn test_oneshot_creates_span() { .with_target("tokio::sync::oneshot"); let (subscriber, handle) = subscriber::mock() - .new_span(oneshot_span.clone().with_explicit_parent(None)) - .enter(oneshot_span.clone()) + .new_span( + oneshot_span + .clone() + .with_ancestry(expect::is_explicit_root()), + ) + .enter(&oneshot_span) .event(initial_tx_dropped_event) - .exit(oneshot_span.clone()) - .enter(oneshot_span.clone()) + .exit(&oneshot_span) + .enter(&oneshot_span) .event(initial_rx_dropped_event) - .exit(oneshot_span.clone()) - .enter(oneshot_span.clone()) + .exit(&oneshot_span) + .enter(&oneshot_span) .event(value_sent_event) - .exit(oneshot_span.clone()) - .enter(oneshot_span.clone()) + .exit(&oneshot_span) + .enter(&oneshot_span) .event(value_received_event) - .exit(oneshot_span.clone()) - .enter(oneshot_span.clone()) - .new_span(async_op_span.clone()) - .exit(oneshot_span.clone()) - .enter(async_op_span.clone()) - .new_span(async_op_poll_span.clone()) - .exit(async_op_span.clone()) - .enter(oneshot_span.clone()) + .exit(&oneshot_span) + .enter(&oneshot_span) + .new_span( + async_op_span + .clone() + .with_ancestry(expect::has_contextual_parent(&oneshot_span_id)), + ) + .exit(&oneshot_span) + .enter(&async_op_span) + .new_span( + async_op_poll_span + .clone() + .with_ancestry(expect::has_contextual_parent(&async_op_span_id)), + ) + .exit(&async_op_span) + .enter(&oneshot_span) .event(final_tx_dropped_event) - .exit(oneshot_span.clone()) - .enter(oneshot_span.clone()) + .exit(&oneshot_span) + .enter(&oneshot_span) .event(final_rx_dropped_event) - .exit(oneshot_span.clone()) + .exit(&oneshot_span) .drop_span(oneshot_span) .drop_span(async_op_span) - .drop_span(async_op_poll_span) + .drop_span(&async_op_poll_span) .run_with_handle(); { @@ -176,7 +202,7 @@ async fn test_rwlock_creates_span() { let current_readers_event = expect::event() .with_target("runtime::resource::state_update") - .with_fields(expect::field("current_readers").with_value(&0)); + .with_fields(expect::field("current_readers").with_value(&0_i64)); let batch_semaphore_span = expect::span() .named("runtime.resource") @@ -188,7 +214,11 @@ async fn test_rwlock_creates_span() { .with_fields(expect::field("permits.op").with_value(&"override")); let (subscriber, handle) = subscriber::mock() - .new_span(rwlock_span.clone().with_explicit_parent(None)) + .new_span( + rwlock_span + .clone() + .with_ancestry(expect::is_explicit_root()), + ) .enter(rwlock_span.clone()) .event(max_readers_event) .event(write_locked_event) @@ -228,7 +258,11 @@ async fn test_semaphore_creates_span() { .with_fields(expect::field("permits.op").with_value(&"override")); let (subscriber, handle) = subscriber::mock() - .new_span(semaphore_span.clone().with_explicit_parent(None)) + .new_span( + semaphore_span + .clone() + .with_ancestry(expect::is_explicit_root()), + ) .enter(semaphore_span.clone()) .new_span(batch_semaphore_span.clone()) .enter(batch_semaphore_span.clone()) diff --git a/tokio/tests/tracing-instrumentation/tests/task.rs b/tokio/tests/tracing_task.rs similarity index 90% rename from tokio/tests/tracing-instrumentation/tests/task.rs rename to tokio/tests/tracing_task.rs index fb215ca7ce0..a9317bf5b12 100644 --- a/tokio/tests/tracing-instrumentation/tests/task.rs +++ b/tokio/tests/tracing_task.rs @@ -2,6 +2,8 @@ //! //! These tests ensure that the instrumentation for task spawning and task //! lifecycles is correct. +#![warn(rust_2018_idioms)] +#![cfg(all(tokio_unstable, feature = "tracing", target_has_atomic = "64"))] use std::{mem, time::Duration}; @@ -15,12 +17,12 @@ async fn task_spawn_creates_span() { .with_target("tokio::task"); let (subscriber, handle) = subscriber::mock() - .new_span(task_span.clone()) - .enter(task_span.clone()) - .exit(task_span.clone()) + .new_span(&task_span) + .enter(&task_span) + .exit(&task_span) // The task span is entered once more when it gets dropped - .enter(task_span.clone()) - .exit(task_span.clone()) + .enter(&task_span) + .exit(&task_span) .drop_span(task_span) .run_with_handle(); @@ -39,7 +41,7 @@ async fn task_spawn_loc_file_recorded() { let task_span = expect::span() .named("runtime.spawn") .with_target("tokio::task") - .with_field(expect::field("loc.file").with_value(&file!())); + .with_fields(expect::field("loc.file").with_value(&file!())); let (subscriber, handle) = subscriber::mock().new_span(task_span).run_with_handle(); @@ -78,7 +80,7 @@ async fn task_builder_loc_file_recorded() { let task_span = expect::span() .named("runtime.spawn") .with_target("tokio::task") - .with_field(expect::field("loc.file").with_value(&file!())); + .with_fields(expect::field("loc.file").with_value(&file!())); let (subscriber, handle) = subscriber::mock().new_span(task_span).run_with_handle(); @@ -105,7 +107,7 @@ async fn task_spawn_sizes_recorded() { .with_target("tokio::task") // TODO(hds): check that original_size.bytes is NOT recorded when this can be done in // tracing-mock without listing every other field. - .with_field(expect::field("size.bytes").with_value(&size)); + .with_fields(expect::field("size.bytes").with_value(&size)); let (subscriber, handle) = subscriber::mock().new_span(task_span).run_with_handle(); @@ -149,7 +151,7 @@ async fn task_big_spawn_sizes_recorded() { let task_span = expect::span() .named("runtime.spawn") .with_target("tokio::task") - .with_field( + .with_fields( expect::field("size.bytes") .with_value(&boxed_size) .and(expect::field("original_size.bytes").with_value(&size)), @@ -178,7 +180,7 @@ fn expect_task_named(name: &str) -> NewSpan { expect::span() .named("runtime.spawn") .with_target("tokio::task") - .with_field( + .with_fields( expect::field("task.name").with_value(&tracing::field::debug(format_args!("{}", name))), ) } diff --git a/tokio/tests/tracing-instrumentation/tests/time.rs b/tokio/tests/tracing_time.rs similarity index 63% rename from tokio/tests/tracing-instrumentation/tests/time.rs rename to tokio/tests/tracing_time.rs index 4d5701598df..f251cc780b9 100644 --- a/tokio/tests/tracing-instrumentation/tests/time.rs +++ b/tokio/tests/tracing_time.rs @@ -2,13 +2,18 @@ //! //! These tests ensure that the instrumentation for tokio //! synchronization primitives is correct. +#![warn(rust_2018_idioms)] +#![cfg(all(tokio_unstable, feature = "tracing", target_has_atomic = "64"))] + use std::time::Duration; use tracing_mock::{expect, subscriber}; #[tokio::test] async fn test_sleep_creates_span() { + let sleep_span_id = expect::id(); let sleep_span = expect::span() + .with_id(sleep_span_id.clone()) .named("runtime.resource") .with_target("tokio::time::sleep"); @@ -16,11 +21,16 @@ async fn test_sleep_creates_span() { .with_target("runtime::resource::state_update") .with_fields( expect::field("duration") - .with_value(&(7_u64 + 1)) + // FIXME(hds): This value isn't stable and doesn't seem to make sense. We're not + // going to test on it until the resource instrumentation has been + // refactored and we know that we're getting a stable value here. + //.with_value(&(7_u64 + 1)) .and(expect::field("duration.op").with_value(&"override")), ); + let async_op_span_id = expect::id(); let async_op_span = expect::span() + .with_id(async_op_span_id.clone()) .named("runtime.resource.async_op") .with_target("tokio::time::sleep"); @@ -29,21 +39,21 @@ async fn test_sleep_creates_span() { .with_target("tokio::time::sleep"); let (subscriber, handle) = subscriber::mock() - .new_span(sleep_span.clone().with_explicit_parent(None)) + .new_span(sleep_span.clone().with_ancestry(expect::is_explicit_root())) .enter(sleep_span.clone()) .event(state_update) .new_span( async_op_span .clone() - .with_contextual_parent(Some("runtime.resource")) - .with_field(expect::field("source").with_value(&"Sleep::new_timeout")), + .with_ancestry(expect::has_contextual_parent(&sleep_span_id)) + .with_fields(expect::field("source").with_value(&"Sleep::new_timeout")), ) .exit(sleep_span.clone()) .enter(async_op_span.clone()) .new_span( async_op_poll_span .clone() - .with_contextual_parent(Some("runtime.resource.async_op")), + .with_ancestry(expect::has_contextual_parent(&async_op_span_id)), ) .exit(async_op_span.clone()) .drop_span(async_op_span) diff --git a/tokio/tests/udp.rs b/tokio/tests/udp.rs index d3d88b147ef..df6f34fb8e8 100644 --- a/tokio/tests/udp.rs +++ b/tokio/tests/udp.rs @@ -1,5 +1,6 @@ #![warn(rust_2018_idioms)] #![cfg(all(feature = "full", not(target_os = "wasi"), not(miri)))] // Wasi does not support bind or UDP + // No `socket` on miri. use std::future::poll_fn; use std::io; @@ -415,7 +416,7 @@ async fn try_send_recv() { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } @@ -431,7 +432,7 @@ async fn try_send_recv() { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } } @@ -457,7 +458,7 @@ async fn try_send_to_recv_from() { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } @@ -474,7 +475,7 @@ async fn try_send_to_recv_from() { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } } @@ -502,7 +503,7 @@ async fn try_recv_buf() { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } @@ -518,7 +519,7 @@ async fn try_recv_buf() { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } } @@ -561,7 +562,7 @@ async fn try_recv_buf_from() { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } @@ -578,7 +579,7 @@ async fn try_recv_buf_from() { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } } @@ -621,7 +622,7 @@ async fn poll_ready() { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } @@ -638,7 +639,7 @@ async fn poll_ready() { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } } diff --git a/tokio/tests/uds_cred.rs b/tokio/tests/uds_cred.rs index 7facffaed63..4f08d764e26 100644 --- a/tokio/tests/uds_cred.rs +++ b/tokio/tests/uds_cred.rs @@ -1,6 +1,6 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "full")] -#![cfg(all(unix, not(target_os = "dragonfly"), not(miri)))] +#![cfg(all(unix, not(target_os = "dragonfly"), not(miri)))] // No `getsockopt` on miri. use tokio::net::UnixStream; diff --git a/tokio/tests/uds_datagram.rs b/tokio/tests/uds_datagram.rs index 9af7a8824ea..2dfdc76f380 100644 --- a/tokio/tests/uds_datagram.rs +++ b/tokio/tests/uds_datagram.rs @@ -1,7 +1,6 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "full")] #![cfg(unix)] -#![cfg(not(miri))] use tokio::io::ReadBuf; use tokio::net::UnixDatagram; @@ -22,6 +21,7 @@ async fn echo_server(socket: UnixDatagram) -> io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn echo() -> io::Result<()> { let dir = tempfile::tempdir().unwrap(); let server_path = dir.path().join("server.sock"); @@ -46,6 +46,7 @@ async fn echo() -> io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn echo_from() -> io::Result<()> { let dir = tempfile::tempdir().unwrap(); let server_path = dir.path().join("server.sock"); @@ -72,6 +73,7 @@ async fn echo_from() -> io::Result<()> { // Even though we use sync non-blocking io we still need a reactor. #[tokio::test] +#[cfg_attr(miri, ignore)] // No SOCK_DGRAM for `socketpair` in miri. async fn try_send_recv_never_block() -> io::Result<()> { let mut recv_buf = [0u8; 16]; let payload = b"PAYLOAD"; @@ -88,7 +90,7 @@ async fn try_send_recv_never_block() -> io::Result<()> { (io::ErrorKind::WouldBlock, _) => break, (_, Some(libc::ENOBUFS)) => break, _ => { - panic!("unexpected error {:?}", err); + panic!("unexpected error {err:?}"); } }, Ok(len) => { @@ -117,6 +119,7 @@ async fn try_send_recv_never_block() -> io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn split() -> std::io::Result<()> { let dir = tempfile::tempdir().unwrap(); let path = dir.path().join("split.sock"); @@ -141,6 +144,7 @@ async fn split() -> std::io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn send_to_recv_from_poll() -> std::io::Result<()> { let dir = tempfile::tempdir().unwrap(); let sender_path = dir.path().join("sender.sock"); @@ -162,6 +166,7 @@ async fn send_to_recv_from_poll() -> std::io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn send_recv_poll() -> std::io::Result<()> { let dir = tempfile::tempdir().unwrap(); let sender_path = dir.path().join("sender.sock"); @@ -185,6 +190,7 @@ async fn send_recv_poll() -> std::io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn try_send_to_recv_from() -> std::io::Result<()> { let dir = tempfile::tempdir().unwrap(); let server_path = dir.path().join("server.sock"); @@ -206,7 +212,7 @@ async fn try_send_to_recv_from() -> std::io::Result<()> { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } @@ -223,7 +229,7 @@ async fn try_send_to_recv_from() -> std::io::Result<()> { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } } @@ -232,6 +238,7 @@ async fn try_send_to_recv_from() -> std::io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn try_recv_buf_from() -> std::io::Result<()> { let dir = tempfile::tempdir().unwrap(); let server_path = dir.path().join("server.sock"); @@ -253,7 +260,7 @@ async fn try_recv_buf_from() -> std::io::Result<()> { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } @@ -270,7 +277,7 @@ async fn try_recv_buf_from() -> std::io::Result<()> { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } } @@ -279,6 +286,7 @@ async fn try_recv_buf_from() -> std::io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn recv_buf_from() -> std::io::Result<()> { let tmp = tempfile::tempdir()?; @@ -302,6 +310,7 @@ async fn recv_buf_from() -> std::io::Result<()> { // Even though we use sync non-blocking io we still need a reactor. #[tokio::test] +#[cfg_attr(miri, ignore)] // No SOCK_DGRAM for `socketpair` in miri. async fn try_recv_buf_never_block() -> io::Result<()> { let payload = b"PAYLOAD"; let mut count = 0; @@ -317,7 +326,7 @@ async fn try_recv_buf_never_block() -> io::Result<()> { (io::ErrorKind::WouldBlock, _) => break, (_, Some(libc::ENOBUFS)) => break, _ => { - panic!("unexpected error {:?}", err); + panic!("unexpected error {err:?}"); } }, Ok(len) => { @@ -349,6 +358,7 @@ async fn try_recv_buf_never_block() -> io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No SOCK_DGRAM for `socketpair` in miri. async fn recv_buf() -> std::io::Result<()> { // Create the pair of sockets let (sock1, sock2) = UnixDatagram::pair()?; @@ -367,6 +377,7 @@ async fn recv_buf() -> std::io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` on miri. async fn poll_ready() -> io::Result<()> { let dir = tempfile::tempdir().unwrap(); let server_path = dir.path().join("server.sock"); @@ -388,7 +399,7 @@ async fn poll_ready() -> io::Result<()> { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } @@ -405,7 +416,7 @@ async fn poll_ready() -> io::Result<()> { break; } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("{:?}", e), + Err(e) => panic!("{e:?}"), } } } diff --git a/tokio/tests/uds_socket.rs b/tokio/tests/uds_socket.rs index bc3e57b2b21..10ecd0b5760 100644 --- a/tokio/tests/uds_socket.rs +++ b/tokio/tests/uds_socket.rs @@ -1,7 +1,6 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "full")] #![cfg(unix)] -#![cfg(not(miri))] use futures::future::try_join; use std::io; @@ -11,6 +10,7 @@ use tokio::{ }; #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` in miri. async fn datagram_echo_server() -> io::Result<()> { let dir = tempfile::tempdir().unwrap(); let server_path = dir.path().join("server.sock"); @@ -52,6 +52,7 @@ async fn datagram_echo_server() -> io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` in miri. async fn listen_and_stream() -> std::io::Result<()> { let dir = tempfile::Builder::new().tempdir().unwrap(); let sock_path = dir.path().join("connect.sock"); @@ -91,6 +92,7 @@ async fn listen_and_stream() -> std::io::Result<()> { } #[tokio::test] +#[cfg_attr(miri, ignore)] // No `socket` in miri. async fn assert_usage() -> std::io::Result<()> { let datagram_socket = UnixSocket::new_datagram()?; let result = datagram_socket diff --git a/tokio/tests/uds_split.rs b/tokio/tests/uds_split.rs index b6c2fa6f016..4d4b07280ee 100644 --- a/tokio/tests/uds_split.rs +++ b/tokio/tests/uds_split.rs @@ -1,7 +1,7 @@ #![warn(rust_2018_idioms)] #![cfg(feature = "full")] #![cfg(unix)] -#![cfg(not(miri))] +#![cfg(not(miri))] // No `socket` in miri. use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::net::UnixStream; diff --git a/tokio/tests/uds_stream.rs b/tokio/tests/uds_stream.rs index 392bbc0bb6c..9c2ab24b941 100644 --- a/tokio/tests/uds_stream.rs +++ b/tokio/tests/uds_stream.rs @@ -1,7 +1,7 @@ #![cfg(feature = "full")] #![warn(rust_2018_idioms)] #![cfg(unix)] -#![cfg(not(miri))] +#![cfg(not(miri))] // No socket in miri. use std::io; #[cfg(target_os = "android")] @@ -106,7 +106,7 @@ async fn try_read_write() -> std::io::Result<()> { Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { break; } - Err(e) => panic!("error = {:?}", e), + Err(e) => panic!("error = {e:?}"), } } @@ -125,7 +125,7 @@ async fn try_read_write() -> std::io::Result<()> { match server.try_read(&mut read[i..]) { Ok(n) => i += n, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("error = {:?}", e), + Err(e) => panic!("error = {e:?}"), } } @@ -147,7 +147,7 @@ async fn try_read_write() -> std::io::Result<()> { Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { break; } - Err(e) => panic!("error = {:?}", e), + Err(e) => panic!("error = {e:?}"), } } @@ -170,7 +170,7 @@ async fn try_read_write() -> std::io::Result<()> { match server.try_read_vectored(&mut bufs) { Ok(n) => i += n, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("error = {:?}", e), + Err(e) => panic!("error = {e:?}"), } } @@ -343,7 +343,7 @@ async fn try_read_buf() -> std::io::Result<()> { Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { break; } - Err(e) => panic!("error = {:?}", e), + Err(e) => panic!("error = {e:?}"), } } @@ -362,7 +362,7 @@ async fn try_read_buf() -> std::io::Result<()> { match server.try_read_buf(&mut read) { Ok(n) => i += n, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => continue, - Err(e) => panic!("error = {:?}", e), + Err(e) => panic!("error = {e:?}"), } }