diff --git a/.cargo/config.toml b/.cargo/config.toml index a1bc5198..dd92a9d4 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,25 +2,14 @@ lint = "clippy --workspace --tests --examples --bins -- -Dclippy::todo" lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo" -ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture" - # just check the library (without dev deps) ci-check-min = "hack --workspace check --no-default-features" -ci-check-lib = "hack --workspace --feature-powerset --exclude-features=io-uring check" -ci-check-lib-linux = "hack --workspace --feature-powerset check" +ci-check-lib = "hack --workspace --feature-powerset --depth=2 --exclude-features=io-uring check" +ci-check-lib-linux = "hack --workspace --feature-powerset --depth=2 check" # check everything -ci-check = "hack --workspace --feature-powerset --exclude-features=io-uring check --tests --examples" -ci-check-linux = "hack --workspace --feature-powerset check --tests --examples" +ci-check = "hack --workspace --feature-powerset --depth=2 --exclude-features=io-uring check --tests --examples" +ci-check-linux = "hack --workspace --feature-powerset --depth=2 check --tests --examples" # tests avoiding io-uring feature -ci-test = " hack --feature-powerset --exclude=actix-rt --exclude=actix-server --exclude-features=io-uring test --workspace --lib --tests --no-fail-fast -- --nocapture" -ci-test-rt = " hack --feature-powerset --exclude-features=io-uring test --package=actix-rt --lib --tests --no-fail-fast -- --nocapture" -ci-test-server = "hack --feature-powerset --exclude-features=io-uring test --package=actix-server --lib --tests --no-fail-fast -- --nocapture" - -# test with io-uring feature -ci-test-rt-linux = " hack --feature-powerset test --package=actix-rt --lib --tests --no-fail-fast -- --nocapture" -ci-test-server-linux = "hack --feature-powerset test --package=actix-server --lib --tests --no-fail-fast -- --nocapture" - -# test lower msrv -ci-test-lower-msrv = "hack --workspace --exclude=actix-server --exclude=actix-tls --feature-powerset test --lib --tests --no-fail-fast -- --nocapture" +ci-test = "hack --feature-powerset --depth=2 --exclude-features=io-uring test --lib --tests --no-fail-fast -- --nocapture" diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 00000000..91c31177 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,4 @@ +disallowed-names = [ + "..", # defaults + "e", # prefer `err` +] diff --git a/.cspell.yml b/.cspell.yml new file mode 100644 index 00000000..2221c11e --- /dev/null +++ b/.cspell.yml @@ -0,0 +1,20 @@ +version: "0.2" +words: + - actix + - addrs + - bytestring + - clippy + - deque + - itertools + - mptcp + - MSRV + - nonblocking + - oneshot + - pemfile + - rcgen + - Rustls + - rustup + - serde + - spki + - uring + - webpki diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..3550a30f --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..f819f7c4 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [JohnTitor] diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e32d54ae..5f7542cc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,10 +1,12 @@ ## PR Type + + INSERT_PR_TYPE - ## PR Checklist + Check your PR fulfills the following: @@ -14,11 +16,10 @@ Check your PR fulfills the following: - [ ] A changelog entry has been made for the appropriate packages. - [ ] Format code with the latest stable rustfmt - ## Overview + - diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..3aeae6b1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + - package-ecosystem: cargo + directory: / + schedule: + interval: weekly + versioning-strategy: lockfile-only diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml deleted file mode 100644 index c9b5c0ab..00000000 --- a/.github/workflows/ci-master.yml +++ /dev/null @@ -1,145 +0,0 @@ -name: CI (master only) - -on: - push: - branches: [master] - -jobs: - build_and_test_nightly: - strategy: - fail-fast: false - matrix: - target: - - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } - - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc } - - { name: Windows (MinGW), os: windows-latest, triple: x86_64-pc-windows-gnu } - - { name: Windows (32-bit), os: windows-latest, triple: i686-pc-windows-msvc } - version: - - nightly - - name: ${{ matrix.target.name }} / ${{ matrix.version }} - runs-on: ${{ matrix.target.os }} - - env: - VCPKGRS_DYNAMIC: 1 - - steps: - - name: Setup Routing - if: matrix.target.os == 'macos-latest' - run: sudo ifconfig lo0 alias 127.0.0.3 - - - uses: actions/checkout@v2 - - # install OpenSSL on Windows - - name: Set vcpkg root - if: matrix.target.triple == 'x86_64-pc-windows-msvc' || matrix.target.triple == 'i686-pc-windows-msvc' - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append - - name: Install OpenSSL - if: matrix.target.triple == 'x86_64-pc-windows-msvc' - run: vcpkg install openssl:x64-windows - - name: Install OpenSSL - if: matrix.target.triple == 'i686-pc-windows-msvc' - run: vcpkg install openssl:x86-windows - - - name: Install ${{ matrix.version }} - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.version }}-${{ matrix.target.triple }} - profile: minimal - override: true - - # - name: Install MSYS2 - # if: matrix.target.triple == 'x86_64-pc-windows-gnu' - # uses: msys2/setup-msys2@v2 - # - name: Install MinGW Packages - # if: matrix.target.triple == 'x86_64-pc-windows-gnu' - # run: | - # msys2 -c 'pacman -Sy --noconfirm pacman' - # msys2 -c 'pacman --noconfirm -S base-devel pkg-config' - - # - name: Generate Cargo.lock - # uses: actions-rs/cargo@v1 - # with: { command: generate-lockfile } - # - name: Cache Dependencies - # uses: Swatinem/rust-cache@v1.2.0 - - - name: Install cargo-hack - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-hack - - - name: check lib - if: > - matrix.target.os != 'ubuntu-latest' - && matrix.target.triple != 'x86_64-pc-windows-gnu' - uses: actions-rs/cargo@v1 - with: { command: ci-check-lib } - - name: check lib - if: matrix.target.os == 'ubuntu-latest' - uses: actions-rs/cargo@v1 - with: { command: ci-check-lib-linux } - - name: check lib - if: matrix.target.triple == 'x86_64-pc-windows-gnu' - uses: actions-rs/cargo@v1 - with: { command: ci-check-min } - - - name: check full - # TODO: compile OpenSSL and run tests on MinGW - if: > - matrix.target.os != 'ubuntu-latest' - && matrix.target.triple != 'x86_64-pc-windows-gnu' - uses: actions-rs/cargo@v1 - with: { command: ci-check } - - name: check all - if: matrix.target.os == 'ubuntu-latest' - uses: actions-rs/cargo@v1 - with: { command: ci-check-linux } - - - name: tests - if: > - matrix.target.os != 'ubuntu-latest' - && matrix.target.triple != 'x86_64-pc-windows-gnu' - run: | - cargo ci-test - cargo ci-test-rt - cargo ci-test-server - - name: tests - if: matrix.target.os == 'ubuntu-latest' - run: | - sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-rt-linux && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-server-linux" - - - name: Clear the cargo caches - run: | - cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean - cargo-cache - - coverage: - name: coverage - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install Rust (nightly) - uses: actions-rs/toolchain@v1 - with: - toolchain: stable-x86_64-unknown-linux-gnu - profile: minimal - override: true - - - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.3.0 - - - name: Generate coverage file - if: github.ref == 'refs/heads/master' - run: | - cargo install cargo-tarpaulin - cargo tarpaulin --out Xml --verbose - - name: Upload to Codecov - if: github.ref == 'refs/heads/master' - uses: codecov/codecov-action@v1 - with: { file: cobertura.xml } diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml new file mode 100644 index 00000000..b6c36682 --- /dev/null +++ b/.github/workflows/ci-post-merge.yml @@ -0,0 +1,129 @@ +name: CI (post-merge) + +on: + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build_and_test_nightly: + strategy: + fail-fast: false + matrix: + # prettier-ignore + target: + - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } + - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } + - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc } + - { name: Windows (MinGW), os: windows-latest, triple: x86_64-pc-windows-gnu } + version: + - nightly + + name: ${{ matrix.target.name }} / ${{ matrix.version }} + runs-on: ${{ matrix.target.os }} + + env: {} + + steps: + - name: Setup Routing + if: matrix.target.os == 'macos-latest' + run: sudo ifconfig lo0 alias 127.0.0.3 + + - uses: actions/checkout@v6 + + - name: Free Disk Space + if: matrix.target.os == 'ubuntu-latest' + run: ./scripts/free-disk-space.sh + + - name: Setup mold linker + if: matrix.target.os == 'ubuntu-latest' + uses: rui314/setup-mold@v1 + + - name: Install nasm + if: matrix.target.os == 'windows-latest' + uses: ilammy/setup-nasm@v1.5.2 + + - name: Install OpenSSL + if: matrix.target.os == 'windows-latest' + shell: bash + run: | + set -e + choco install openssl --version=1.1.1.2100 -y --no-progress + echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV + echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV + + - name: Install Rust (${{ matrix.version }}) + uses: actions-rust-lang/setup-rust-toolchain@v1.15.2 + with: + toolchain: ${{ matrix.version }} + + - name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean + uses: taiki-e/install-action@v2.67.18 + with: + tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean + + - name: check lib + if: > + matrix.target.os != 'ubuntu-latest' + && matrix.target.triple != 'x86_64-pc-windows-gnu' + run: cargo ci-check-lib + - name: check lib + if: matrix.target.os == 'ubuntu-latest' + run: cargo ci-check-lib-linux + - name: check lib + if: matrix.target.triple == 'x86_64-pc-windows-gnu' + run: cargo ci-check-min + + - name: check full + # TODO: compile OpenSSL and run tests on MinGW + if: > + matrix.target.os != 'ubuntu-latest' + && matrix.target.triple != 'x86_64-pc-windows-gnu' + run: cargo ci-check + - name: check all + if: matrix.target.os == 'ubuntu-latest' + run: cargo ci-check-linux + + - name: tests + run: just test + + # TODO: re-instate some io-uring tests PRs + # - name: tests + # if: matrix.target.os == 'ubuntu-latest' + # run: >- + # sudo bash -c " + # ulimit -Sl 512 + # && ulimit -Hl 512 + # && PATH=$PATH:/usr/share/rust/.cargo/bin + # && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-rustls-020 + # && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-rustls-021 + # && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-linux + # " + + - name: CI cache clean + run: cargo-ci-cache-clean + + minimal-versions: + name: minimal versions + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install Rust (nightly) + uses: actions-rust-lang/setup-rust-toolchain@v1.15.2 + with: + toolchain: nightly + + - name: Install cargo-hack & cargo-minimal-versions + uses: taiki-e/install-action@v2.67.18 + with: + tool: cargo-hack,cargo-minimal-versions + + - name: Check With Minimal Versions + run: cargo minimal-versions check diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf631e7b..b0fdfd2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,171 +1,133 @@ name: CI on: - pull_request: - types: [opened, synchronize, reopened] - push: - branches: [master] + pull_request: {} + merge_group: { types: [checks_requested] } + push: { branches: [main] } + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: + read_msrv: + name: Read MSRV + uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@v0.1.0 + build_and_test: + needs: + - read_msrv + strategy: fail-fast: false matrix: + # prettier-ignore target: - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc } - { name: Windows (MinGW), os: windows-latest, triple: x86_64-pc-windows-gnu } - - { name: Windows (32-bit), os: windows-latest, triple: i686-pc-windows-msvc } version: - - 1.52.0 # MSRV for -server and -tls - - stable + - { name: msrv, version: "${{ needs.read_msrv.outputs.msrv }}" } + - { name: stable, version: stable } - name: ${{ matrix.target.name }} / ${{ matrix.version }} + name: ${{ matrix.target.name }} / ${{ matrix.version.name }} runs-on: ${{ matrix.target.os }} - env: - VCPKGRS_DYNAMIC: 1 + env: {} steps: - name: Setup Routing if: matrix.target.os == 'macos-latest' run: sudo ifconfig lo0 alias 127.0.0.3 - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 + + - name: Free Disk Space + if: matrix.target.os == 'ubuntu-latest' + run: ./scripts/free-disk-space.sh + + - name: Setup mold linker + if: matrix.target.os == 'ubuntu-latest' + uses: rui314/setup-mold@v1 + + - name: Install nasm + if: matrix.target.os == 'windows-latest' + uses: ilammy/setup-nasm@v1.5.2 - # install OpenSSL on Windows - - name: Set vcpkg root - if: matrix.target.triple == 'x86_64-pc-windows-msvc' || matrix.target.triple == 'i686-pc-windows-msvc' - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append - name: Install OpenSSL - if: matrix.target.triple == 'x86_64-pc-windows-msvc' - run: vcpkg install openssl:x64-windows - - name: Install OpenSSL - if: matrix.target.triple == 'i686-pc-windows-msvc' - run: vcpkg install openssl:x86-windows + if: matrix.target.os == 'windows-latest' + shell: bash + run: | + set -e + choco install openssl --version=1.1.1.2100 -y --no-progress + echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV + echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV - - name: Install ${{ matrix.version }} - uses: actions-rs/toolchain@v1 + - name: Install Rust (${{ matrix.version.name }}) + uses: actions-rust-lang/setup-rust-toolchain@v1.15.2 with: - toolchain: ${{ matrix.version }}-${{ matrix.target.triple }} - profile: minimal - override: true + toolchain: ${{ matrix.version.version }} - # - name: Install MSYS2 - # if: matrix.target.triple == 'x86_64-pc-windows-gnu' - # uses: msys2/setup-msys2@v2 - # - name: Install MinGW Packages - # if: matrix.target.triple == 'x86_64-pc-windows-gnu' - # run: | - # msys2 -c 'pacman -Sy --noconfirm pacman' - # msys2 -c 'pacman --noconfirm -S base-devel pkg-config' - - # - name: Generate Cargo.lock - # uses: actions-rs/cargo@v1 - # with: { command: generate-lockfile } - # - name: Cache Dependencies - # uses: Swatinem/rust-cache@v1.2.0 - - - name: Install cargo-hack - uses: actions-rs/cargo@v1 + - name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean + uses: taiki-e/install-action@v2.67.18 with: - command: install - args: cargo-hack + tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean + + - name: Generate Cargo.lock + run: cargo generate-lockfile + + - name: workaround MSRV issues + if: matrix.version.name == 'msrv' + run: just downgrade-for-msrv - name: check lib if: > matrix.target.os != 'ubuntu-latest' && matrix.target.triple != 'x86_64-pc-windows-gnu' - uses: actions-rs/cargo@v1 - with: { command: ci-check-lib } + run: cargo ci-check-lib - name: check lib if: matrix.target.os == 'ubuntu-latest' - uses: actions-rs/cargo@v1 - with: { command: ci-check-lib-linux } + run: cargo ci-check-lib-linux - name: check lib - if: matrix.target.triple == 'x86_64-pc-windows-gnu' - uses: actions-rs/cargo@v1 - with: { command: ci-check-min } + if: matrix.target.triple != 'x86_64-pc-windows-gnu' + run: cargo ci-check-min - name: check full # TODO: compile OpenSSL and run tests on MinGW if: > matrix.target.os != 'ubuntu-latest' && matrix.target.triple != 'x86_64-pc-windows-gnu' - uses: actions-rs/cargo@v1 - with: { command: ci-check } + run: cargo ci-check - name: check all if: matrix.target.os == 'ubuntu-latest' - uses: actions-rs/cargo@v1 - with: { command: ci-check-linux } + run: cargo ci-check-linux - name: tests - if: > - matrix.target.os != 'ubuntu-latest' - && matrix.target.triple != 'x86_64-pc-windows-gnu' - run: | - cargo ci-test - cargo ci-test-rt - cargo ci-test-server - - name: tests - if: matrix.target.os == 'ubuntu-latest' - run: | - sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-rt-linux && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo ci-test-server-linux" + run: just test - - name: Clear the cargo caches - run: | - cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean - cargo-cache + - name: CI cache clean + run: cargo-ci-cache-clean - build_and_test_lower_msrv: - name: Linux / 1.46 (lower MSRV) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install 1.46.0 # MSRV for all but -server and -tls - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.46.0-x86_64-unknown-linux-gnu - profile: minimal - override: true - - - name: Install cargo-hack - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-hack - - - name: tests - run: | - sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=1.46 cargo ci-test-lower-msrv" - - - name: Clear the cargo caches - run: | - cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean - cargo-cache - - rustdoc: - name: rustdoc + docs: + name: Documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Install Rust (nightly) - uses: actions-rs/toolchain@v1 + uses: actions-rust-lang/setup-rust-toolchain@v1.15.2 with: - toolchain: nightly-x86_64-unknown-linux-gnu - profile: minimal - override: true + toolchain: nightly - - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.3.0 + - name: Install just + uses: taiki-e/install-action@v2.67.18 + with: + tool: just - - name: doc tests io-uring - run: | - sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=nightly cargo ci-doctest" + - name: doc tests + run: just test-docs diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml deleted file mode 100644 index ca637beb..00000000 --- a/.github/workflows/clippy-fmt.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Lint - -on: - pull_request: - types: [opened, synchronize, reopened] - -jobs: - fmt: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - components: rustfmt - override: true - - name: Rustfmt Check - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - clippy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - components: clippy - override: true - - name: Clippy Check - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --workspace --all-features --tests --examples --bins -- -Dclippy::todo diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..6738a9d7 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,39 @@ +name: Coverage + +on: + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1.15.2 + with: + components: llvm-tools-preview + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@v2.67.18 + with: + tool: cargo-llvm-cov + + - name: Generate code coverage + run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5.5.2 + with: + files: codecov.json + fail_ci_if_error: true + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..c9e415a6 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,69 @@ +name: Lint + +on: + pull_request: {} + merge_group: { types: [checks_requested] } + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + fmt: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - uses: actions-rust-lang/setup-rust-toolchain@v1.15.2 + with: + toolchain: nightly + components: rustfmt + + - name: Rustfmt Check + run: cargo fmt --all -- --check + + clippy: + permissions: + contents: write + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - uses: actions-rust-lang/setup-rust-toolchain@v1.15.2 + with: { components: clippy } + + - uses: giraffate/clippy-action@v1.0.1 + with: + reporter: "github-pr-check" + github_token: ${{ secrets.GITHUB_TOKEN }} + clippy_flags: --workspace --all-features --tests --examples --bins -- -Dclippy::todo -Aunknown_lints + + check-external-types: + if: false # rustdoc mismatch currently + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install Rust (${{ vars.RUST_VERSION_EXTERNAL_TYPES }}) + uses: actions-rust-lang/setup-rust-toolchain@v1.15.2 + with: + toolchain: ${{ vars.RUST_VERSION_EXTERNAL_TYPES }} + + - name: Install just + uses: taiki-e/install-action@v2.67.18 + with: + tool: just + + - name: Install cargo-check-external-types + uses: taiki-e/cache-cargo-install-action@v3.0.1 + with: + tool: cargo-check-external-types + + - name: check external types + run: just check-external-types-all +${{ vars.RUST_VERSION_EXTERNAL_TYPES }} diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml deleted file mode 100644 index 36044230..00000000 --- a/.github/workflows/upload-doc.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Upload documentation - -on: - push: - branches: [master] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly-x86_64-unknown-linux-gnu - profile: minimal - override: true - - - name: Build Docs - uses: actions-rs/cargo@v1 - with: - command: doc - args: --workspace --all-features --no-deps - - - name: Tweak HTML - run: echo '' > target/doc/index.html - - - name: Deploy to GitHub Pages - uses: JamesIves/github-pages-deploy-action@3.7.1 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: gh-pages - FOLDER: target/doc diff --git a/.gitignore b/.gitignore index a6909f1f..5e9a0452 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -Cargo.lock target/ guide/build/ /gh-pages @@ -13,4 +12,8 @@ guide/build/ # These are backup files generated by rustfmt **/*.rs.bk +# IDEs .idea + +# direnv +/.direnv diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 00000000..71b9be3a --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,3 @@ +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +use_field_init_shorthand = true diff --git a/.taplo.toml b/.taplo.toml new file mode 100644 index 00000000..195e07b3 --- /dev/null +++ b/.taplo.toml @@ -0,0 +1,29 @@ +exclude = ["target/*"] +include = ["**/*.toml"] + +[formatting] +column_width = 110 + +[[rule]] +include = ["**/Cargo.toml"] +keys = [ + "dependencies", + "*-dependencies", + "workspace.dependencies", + "workspace.*-dependencies", + "target.*.dependencies", + "target.*.*-dependencies", +] +formatting.reorder_keys = true + +[[rule]] +include = ["**/Cargo.toml"] +keys = [ + "dependencies.*", + "*-dependencies.*", + "workspace.dependencies.*", + "workspace.*-dependencies.*", + "target.*.dependencies", + "target.*.*-dependencies", +] +formatting.reorder_keys = false diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..b1d9e0ae --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,32 @@ +{ + "rust-analyzer.cargo.features": [ + "accept", + "actix-macros", + "connect", + "default", + "macros", + "native-tls", + "openssl", + "rustls", + "rustls-021", + "rustls-0_20", + "rustls-0_20-native-roots", + "rustls-0_20-webpki-roots", + "rustls-0_21", + "rustls-0_21-native-roots", + "rustls-0_21-webpki-roots", + "rustls-0_22", + "rustls-0_22-native-roots", + "rustls-0_22-webpki-roots", + "rustls-0_23", + "rustls-0_23-native-roots", + "rustls-0_23-webpki-roots", + "rustls-webpki-0101", + "serde", + "tokio-rustls-023", + "tokio-rustls-024", + "uri", + "webpki-roots-022", + "webpki-roots-025", + ] +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index ae97b324..9628fb82 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities @@ -39,7 +39,7 @@ Instances of abusive, harassing, or otherwise unacceptable behavior may be repor Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. [@robjtede]: https://github.com/robjtede -[@JohnTitor]: https://github.com/JohnTitor +[@johntitor]: https://github.com/JohnTitor ## Attribution diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..8f51bb29 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2858 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "actix-codec" +version = "0.5.2" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "criterion", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-test", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +dependencies = [ + "actix-rt", + "futures-util", + "proc-macro2", + "quote", + "rustversion-msrv", + "syn", + "trybuild", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +dependencies = [ + "actix-macros", + "futures-core", + "tokio", + "tokio-uring", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "bytes", + "futures-core", + "futures-util", + "mio", + "pretty_env_logger", + "socket2 0.6.2", + "static_assertions", + "tokio", + "tokio-uring", + "tokio-util", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +dependencies = [ + "actix-rt", + "actix-utils", + "futures-core", + "futures-util", + "pin-project-lite", +] + +[[package]] +name = "actix-tls" +version = "3.5.0" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "bytes", + "futures-core", + "futures-util", + "hickory-resolver", + "http 0.2.12", + "http 1.4.0", + "impl-more", + "itertools 0.14.0", + "openssl", + "pin-project-lite", + "pretty_env_logger", + "rcgen", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "rustls-native-certs 0.8.3", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "rustls-webpki 0.101.7", + "tokio", + "tokio-native-tls", + "tokio-openssl", + "tokio-rustls 0.23.4", + "tokio-rustls 0.24.1", + "tokio-rustls 0.25.0", + "tokio-rustls 0.26.4", + "tokio-util", + "tracing", + "webpki-roots 0.22.6", + "webpki-roots 0.25.4", + "webpki-roots 0.26.11", +] + +[[package]] +name = "actix-tracing" +version = "0.1.0" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "slab", + "tracing", + "tracing-futures", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +dependencies = [ + "actix-rt", + "futures-util", + "local-waker", + "pin-project-lite", + "static_assertions", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "bytestring" +version = "1.5.0" +dependencies = [ + "ahash", + "bytes", + "serde_core", + "serde_json", + "static_assertions", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand", + "ring 0.17.14", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "io-uring" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595a0399f411a508feb2ec1e970a4a30c249351e30208960d58298de8660b0e5" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.10", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "local-channel" +version = "0.1.5" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "local-waker", + "tokio", +] + +[[package]] +name = "local-waker" +version = "0.1.4" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moka" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe 0.1.6", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_env_logger" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring 0.17.14", + "rustls-pki-types", + "time", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log", + "ring 0.16.20", + "sct", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring 0.17.14", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring 0.17.14", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.103.9", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe 0.1.6", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring 0.17.14", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring 0.17.14", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rustversion-msrv" +version = "0.100.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6ceb60223ee771fb5dfe462e29e5ee92bca9a7b9c555584f4d361045dae0e12" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "target-triple" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-openssl" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59df6849caa43bb7567f9a36f863c447d95a11d5903c9cc334ba32576a27eadd" +dependencies = [ + "openssl", + "openssl-sys", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.9", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.36", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-test" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" +dependencies = [ + "futures-core", + "tokio", + "tokio-stream", +] + +[[package]] +name = "tokio-uring" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "748482e3e13584a34664a710168ad5068e8cb1d968aa4ffa887e83ca6dd27967" +dependencies = [ + "futures-util", + "io-uring", + "libc", + "slab", + "socket2 0.4.10", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f614c21bd3a61bad9501d75cbb7686f00386c806d7f456778432c25cf86948a" +dependencies = [ + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.14", + "untrusted 0.9.0", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.6", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/Cargo.toml b/Cargo.toml index eb452e1a..f38ba50f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "actix-codec", "actix-macros", @@ -13,6 +14,13 @@ members = [ "local-waker", ] +[workspace.package] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-net" +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.88" + [patch.crates-io] actix-codec = { path = "actix-codec" } actix-macros = { path = "actix-macros" } @@ -30,3 +38,13 @@ local-waker = { path = "local-waker" } lto = true opt-level = 3 codegen-units = 1 + +[workspace.lints.rust] +rust-2018-idioms = "deny" +nonstandard-style = "deny" +future-incompatible = "deny" +missing-docs = { level = "warn", priority = -1 } + +[workspace.lints.clippy] +uninlined-format-args = "warn" +disallowed-names = "warn" diff --git a/README.md b/README.md index 639d6344..742492f6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ > A collection of lower-level libraries for composable network services. [![CI](https://github.com/actix/actix-net/actions/workflows/ci.yml/badge.svg?event=push&style=flat-square)](https://github.com/actix/actix-net/actions/workflows/ci.yml) -[![codecov](https://codecov.io/gh/actix/actix-net/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-net) +[![codecov](https://codecov.io/gh/actix/actix-net/graph/badge.svg?token=8rKIZKtLLm)](https://codecov.io/gh/actix/actix-net) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Dependency Status](https://deps.rs/repo/github/actix/actix-net/status.svg)](https://deps.rs/repo/github/actix/actix-net) @@ -11,10 +11,9 @@ See example folders for [`actix-server`](./actix-server/examples) and [`actix-tls`](./actix-tls/examples). -### MSRV +## MSRV -Most crates in this repo's have a Minimum Supported Rust Version (MSRV) of 1.46.0. Only `actix-tls` -and `actix-server` have MSRV of 1.52.0. +Crates in this repo currently have a Minimum Supported Rust Version (MSRV) of 1.88. As a policy, we permit MSRV increases in non-breaking releases. ## License diff --git a/actix-codec/CHANGES.md b/actix-codec/CHANGES.md index 8f492a3b..2cfe60d2 100644 --- a/actix-codec/CHANGES.md +++ b/actix-codec/CHANGES.md @@ -1,42 +1,52 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased +- Minimum supported Rust version (MSRV) is now 1.88. + +## 0.5.2 + +- Minimum supported Rust version (MSRV) is now 1.65. + +## 0.5.1 + +- Logs emitted now use the `tracing` crate with `log` compatibility. +- Minimum supported Rust version (MSRV) is now 1.49. + +## 0.5.0 + +- Updated `tokio-util` dependency to `0.7.0`. + +## 0.4.2 -## 0.4.2 - 2021-12-31 - No significant changes since `0.4.1`. +## 0.4.1 -## 0.4.1 - 2021-11-05 -- Added `LinesCodec.` [#338] -- `Framed::poll_ready` flushes when the buffer is full. [#409] +- Added `LinesCodec`. +- `Framed::poll_ready` flushes when the buffer is full. -[#338]: https://github.com/actix/actix-net/pull/338 -[#409]: https://github.com/actix/actix-net/pull/409 +## 0.4.0 - -## 0.4.0 - 2021-04-20 - No significant changes since v0.4.0-beta.1. +## 0.4.0-beta.1 -## 0.4.0-beta.1 - 2020-12-28 -- Replace `pin-project` with `pin-project-lite`. [#237] -- Upgrade `tokio` dependency to `1`. [#237] -- Upgrade `tokio-util` dependency to `0.6`. [#237] -- Upgrade `bytes` dependency to `1`. [#237] +- Replace `pin-project` with `pin-project-lite`. +- Upgrade `tokio` dependency to `1`. +- Upgrade `tokio-util` dependency to `0.6`. +- Upgrade `bytes` dependency to `1`. -[#237]: https://github.com/actix/actix-net/pull/237 +## 0.3.0 - -## 0.3.0 - 2020-08-23 - No changes from beta 2. +## 0.3.0-beta.2 -## 0.3.0-beta.2 - 2020-08-19 - Remove unused type parameter from `Framed::replace_codec`. +## 0.3.0-beta.1 -## 0.3.0-beta.1 - 2020-08-19 - Use `.advance()` instead of `.split_to()`. - Upgrade `tokio-util` to `0.3`. - Improve `BytesCodec::encode()` performance. @@ -45,31 +55,31 @@ - Add method on `Framed` to get a pinned reference to the underlying I/O. - Add method on `Framed` check emptiness of read buffer. +## 0.2.0 -## 0.2.0 - 2019-12-10 - Use specific futures dependencies. - ## 0.2.0-alpha.4 + - Fix buffer remaining capacity calculation. - ## 0.2.0-alpha.3 + - Use tokio 0.2. - Fix low/high watermark for write/read buffers. - ## 0.2.0-alpha.2 + - Migrated to `std::future`. +## 0.1.2 -## 0.1.2 - 2019-03-27 - Added `Framed::map_io()` method. +## 0.1.1 -## 0.1.1 - 2019-03-06 - Added `FramedParts::with_read_buffer()` method. +## 0.1.0 -## 0.1.0 - 2018-12-09 - Move codec to separate crate. diff --git a/actix-codec/Cargo.toml b/actix-codec/Cargo.toml index d9920c7d..c6eda83b 100644 --- a/actix-codec/Cargo.toml +++ b/actix-codec/Cargo.toml @@ -1,36 +1,36 @@ [package] name = "actix-codec" -version = "0.4.2" -authors = [ - "Nikolay Kim ", - "Rob Ede ", -] +version = "0.5.2" +authors = ["Nikolay Kim ", "Rob Ede "] description = "Codec utilities for working with framed protocols" keywords = ["network", "framework", "async", "futures"] repository = "https://github.com/actix/actix-net" categories = ["network-programming", "asynchronous"] license = "MIT OR Apache-2.0" -edition = "2018" +edition.workspace = true +rust-version.workspace = true -[lib] -name = "actix_codec" -path = "src/lib.rs" +[package.metadata.cargo_check_external_types] +allowed_external_types = ["bytes::*", "futures_core::*", "futures_sink::*", "tokio::*", "tokio_util::*"] [dependencies] -bitflags = "1.2.1" +bitflags = "2" bytes = "1" futures-core = { version = "0.3.7", default-features = false } futures-sink = { version = "0.3.7", default-features = false } -log = "0.4" memchr = "2.3" pin-project-lite = "0.2" -tokio = "1.13.1" -tokio-util = { version = "0.6", features = ["codec", "io"] } +tokio = "1.44.2" +tokio-util = { version = "0.7", features = ["codec", "io"] } +tracing = { version = "0.1.30", default-features = false, features = ["log"] } [dev-dependencies] -criterion = { version = "0.3", features = ["html_reports"] } +criterion = { version = "0.5", features = ["html_reports"] } tokio-test = "0.4.2" [[bench]] name = "lines" harness = false + +[lints] +workspace = true diff --git a/actix-codec/benches/lines.rs b/actix-codec/benches/lines.rs index e32b8365..c6d13123 100644 --- a/actix-codec/benches/lines.rs +++ b/actix-codec/benches/lines.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs)] + use bytes::BytesMut; use criterion::{criterion_group, criterion_main, Criterion}; diff --git a/actix-codec/src/framed.rs b/actix-codec/src/framed.rs index 73dca006..6d6e1478 100644 --- a/actix-codec/src/framed.rs +++ b/actix-codec/src/framed.rs @@ -18,6 +18,7 @@ const LW: usize = 1024; const HW: usize = 8 * 1024; bitflags! { + #[derive(Debug, Clone, Copy)] struct Flags: u8 { const EOF = 0b0001; const READABLE = 0b0010; @@ -197,11 +198,11 @@ impl Framed { } } - log::trace!("attempting to decode a frame"); + tracing::trace!("attempting to decode a frame"); match this.codec.decode(this.read_buf) { Ok(Some(frame)) => { - log::trace!("frame decoded from buffer"); + tracing::trace!("frame decoded from buffer"); return Poll::Ready(Some(Ok(frame))); } Err(err) => return Poll::Ready(Some(Err(err))), @@ -233,19 +234,16 @@ impl Framed { } /// Flush write buffer to underlying I/O stream. - pub fn flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> + pub fn flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> where T: AsyncWrite, U: Encoder, { let mut this = self.as_mut().project(); - log::trace!("flushing framed transport"); + tracing::trace!("flushing framed transport"); while !this.write_buf.is_empty() { - log::trace!("writing; remaining={}", this.write_buf.len()); + tracing::trace!("writing; remaining={}", this.write_buf.len()); let n = ready!(this.io.as_mut().poll_write(cx, this.write_buf))?; @@ -264,15 +262,12 @@ impl Framed { // Try flushing the underlying IO ready!(this.io.poll_flush(cx))?; - log::trace!("framed transport flushed"); + tracing::trace!("framed transport flushed"); Poll::Ready(Ok(())) } /// Flush write buffer and shutdown underlying I/O stream. - pub fn close( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> + pub fn close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> where T: AsyncWrite, U: Encoder, diff --git a/actix-codec/src/lib.rs b/actix-codec/src/lib.rs index 6914b306..f207c21c 100644 --- a/actix-codec/src/lib.rs +++ b/actix-codec/src/lib.rs @@ -1,25 +1,26 @@ //! Codec utilities for working with framed protocols. //! -//! Contains adapters to go from streams of bytes, [`AsyncRead`] and -//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`]. -//! Framed streams are also known as `transports`. +//! Contains adapters to go from streams of bytes, [`AsyncRead`] and [`AsyncWrite`], to framed +//! streams implementing [`Sink`] and [`Stream`]. Framed streams are also known as `transports`. //! //! [`Sink`]: futures_sink::Sink //! [`Stream`]: futures_core::Stream -#![deny(rust_2018_idioms, nonstandard_style)] -#![warn(future_incompatible, missing_docs)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +pub use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +pub use tokio_util::{ + codec::{Decoder, Encoder}, + io::poll_read_buf, +}; + mod bcodec; mod framed; mod lines; -pub use self::bcodec::BytesCodec; -pub use self::framed::{Framed, FramedParts}; -pub use self::lines::LinesCodec; - -pub use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; -pub use tokio_util::codec::{Decoder, Encoder}; -pub use tokio_util::io::poll_read_buf; +pub use self::{ + bcodec::BytesCodec, + framed::{Framed, FramedParts}, + lines::LinesCodec, +}; diff --git a/actix-codec/src/lines.rs b/actix-codec/src/lines.rs index af399e8f..3c233ace 100644 --- a/actix-codec/src/lines.rs +++ b/actix-codec/src/lines.rs @@ -7,8 +7,8 @@ use super::{Decoder, Encoder}; /// Lines codec. Reads/writes line delimited strings. /// -/// Will split input up by LF or CRLF delimiters. I.e. carriage return characters at the end of -/// lines are not preserved. +/// Will split input up by LF or CRLF delimiters. Carriage return characters at the end of lines are +/// not preserved. #[derive(Debug, Copy, Clone, Default)] #[non_exhaustive] pub struct LinesCodec; diff --git a/actix-codec/tests/test_framed_sink.rs b/actix-codec/tests/test_framed_sink.rs index 15c3c292..063fbfa3 100644 --- a/actix-codec/tests/test_framed_sink.rs +++ b/actix-codec/tests/test_framed_sink.rs @@ -1,12 +1,18 @@ +#![allow(missing_docs)] + +use std::{ + collections::VecDeque, + io::{self, Write}, + pin::Pin, + task::{ + Context, + Poll::{self, Pending, Ready}, + }, +}; + use actix_codec::*; -use bytes::Buf; -use bytes::{BufMut, BytesMut}; +use bytes::{Buf as _, BufMut as _, BytesMut}; use futures_sink::Sink; -use std::collections::VecDeque; -use std::io::{self, Write}; -use std::pin::Pin; -use std::task::Poll::{Pending, Ready}; -use std::task::{Context, Poll}; use tokio_test::{assert_ready, task}; macro_rules! bilateral { @@ -17,26 +23,6 @@ macro_rules! bilateral { }}; } -macro_rules! assert_ready { - ($e:expr) => {{ - use core::task::Poll::*; - match $e { - Ready(v) => v, - Pending => panic!("pending"), - } - }}; - ($e:expr, $($msg:tt),+) => {{ - use core::task::Poll::*; - match $e { - Ready(v) => v, - Pending => { - let msg = format_args!($($msg),+); - panic!("pending; {}", msg) - } - } - }}; -} - #[derive(Debug)] pub struct Bilateral { pub calls: VecDeque>>, @@ -51,7 +37,7 @@ impl Write for Bilateral { Ok(data.len()) } Some(Err(err)) => Err(err), - None => panic!("unexpected write; {:?}", src), + None => panic!("unexpected write; {src:?}"), } } @@ -77,10 +63,7 @@ impl AsyncWrite for Bilateral { other => Ready(other), } } - fn poll_shutdown( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - ) -> Poll> { + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { unimplemented!() } } diff --git a/actix-macros/CHANGES.md b/actix-macros/CHANGES.md index c1b66088..146f69f7 100644 --- a/actix-macros/CHANGES.md +++ b/actix-macros/CHANGES.md @@ -1,46 +1,53 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased +- Minimum supported Rust version (MSRV) is now 1.88. + +## 0.2.4 + +- Update `syn` dependency to `2`. +- Minimum supported Rust version (MSRV) is now 1.65. + +## 0.2.3 -## 0.2.3 - 2021-10-19 - Fix test macro in presence of other imports named "test". [#399] [#399]: https://github.com/actix/actix-net/pull/399 +## 0.2.2 -## 0.2.2 - 2021-10-14 - Improve error recovery potential when macro input is invalid. [#391] - Allow custom `System`s on test macro. [#391] [#391]: https://github.com/actix/actix-net/pull/391 +## 0.2.1 -## 0.2.1 - 2021-02-02 - Add optional argument `system` to `main` macro which can be used to specify the path to `actix_rt::System` (useful for re-exports). [#363] [#363]: https://github.com/actix/actix-net/pull/363 +## 0.2.0 -## 0.2.0 - 2021-02-02 - Update to latest `actix_rt::System::new` signature. [#261] [#261]: https://github.com/actix/actix-net/pull/261 +## 0.2.0-beta.1 -## 0.2.0-beta.1 - 2021-01-09 - Remove `actix-reexport` feature. [#218] [#218]: https://github.com/actix/actix-net/pull/218 +## 0.1.3 -## 0.1.3 - 2020-12-03 - Add `actix-reexport` feature. [#218] [#218]: https://github.com/actix/actix-net/pull/218 +## 0.1.2 -## 0.1.2 - 2020-05-18 - Forward actix_rt::test arguments to test function [#127] [#127]: https://github.com/actix/actix-net/pull/127 diff --git a/actix-macros/Cargo.toml b/actix-macros/Cargo.toml index 75ac8b72..d4bfcfe5 100644 --- a/actix-macros/Cargo.toml +++ b/actix-macros/Cargo.toml @@ -1,27 +1,39 @@ [package] name = "actix-macros" -version = "0.2.3" +version = "0.2.4" authors = [ - "Nikolay Kim ", - "Ibraheem Ahmed ", - "Rob Ede ", + "Nikolay Kim ", + "Ibraheem Ahmed ", + "Rob Ede ", ] description = "Macros for Actix system and runtime" -repository = "https://github.com/actix/actix-net.git" +repository = "https://github.com/actix/actix-net" categories = ["network-programming", "asynchronous"] -license = "MIT OR Apache-2.0" -edition = "2018" +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[package.metadata.cargo-machete] +ignored = [ + "proc_macro2", # specified for minimal versions compat +] [lib] proc-macro = true [dependencies] -quote = "1.0.3" -syn = { version = "^1", features = ["full"] } +quote = "1" +syn = { version = "2", features = ["full"] } + +# minimal versions compat +[target.'cfg(any())'.dependencies] +proc-macro2 = "1.0.60" [dev-dependencies] -actix-rt = "2.0.0" - -futures-util = { version = "0.3.7", default-features = false } -rustversion = "1" +actix-rt = "2" +futures-util = { version = "0.3.17", default-features = false } +rustversion-msrv = "0.100" trybuild = "1" + +[lints] +workspace = true diff --git a/actix-macros/src/lib.rs b/actix-macros/src/lib.rs index 284f6920..f2d96d4c 100644 --- a/actix-macros/src/lib.rs +++ b/actix-macros/src/lib.rs @@ -8,13 +8,14 @@ //! # Tests //! See docs for the [`#[test]`](macro@test) macro. -#![deny(rust_2018_idioms, nonstandard_style)] -#![warn(future_incompatible)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] use proc_macro::TokenStream; use quote::quote; +use syn::parse::Parser as _; + +type AttributeArgs = syn::punctuated::Punctuated; /// Marks async entry-point function to be executed by Actix system. /// @@ -25,9 +26,7 @@ use quote::quote; /// println!("Hello world"); /// } /// ``` -#[allow(clippy::needless_doctest_main)] #[proc_macro_attribute] -#[cfg(not(test))] // Work around for rust-lang/rust#62127 pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { let mut input = match syn::parse::(item.clone()) { Ok(input) => input, @@ -35,7 +34,11 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { Err(err) => return input_and_compile_error(item, err), }; - let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let parser = AttributeArgs::parse_terminated; + let args = match parser.parse(args.clone()) { + Ok(args) => args, + Err(err) => return input_and_compile_error(args, err), + }; let attrs = &input.attrs; let vis = &input.vis; @@ -55,11 +58,15 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { for arg in &args { match arg { - syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { - lit: syn::Lit::Str(lit), + syn::Meta::NameValue(syn::MetaNameValue { path, + value: + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }), .. - })) => match path + }) => match path .get_ident() .map(|i| i.to_string().to_lowercase()) .as_deref() @@ -78,6 +85,7 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { .into(); } }, + _ => { return syn::Error::new_spanned(arg, "Unknown attribute specified") .to_compile_error() @@ -114,7 +122,11 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream { Err(err) => return input_and_compile_error(item, err), }; - let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let parser = AttributeArgs::parse_terminated; + let args = match parser.parse(args.clone()) { + Ok(args) => args, + Err(err) => return input_and_compile_error(args, err), + }; let attrs = &input.attrs; let vis = &input.vis; @@ -123,7 +135,7 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream { let mut has_test_attr = false; for attr in attrs { - if attr.path.is_ident("test") { + if attr.path().is_ident("test") { has_test_attr = true; } } @@ -149,11 +161,15 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream { for arg in &args { match arg { - syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { - lit: syn::Lit::Str(lit), + syn::Meta::NameValue(syn::MetaNameValue { path, + value: + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }), .. - })) => match path + }) => match path .get_ident() .map(|i| i.to_string().to_lowercase()) .as_deref() diff --git a/actix-macros/tests/trybuild.rs b/actix-macros/tests/trybuild.rs index 2af99636..f50dd8c7 100644 --- a/actix-macros/tests/trybuild.rs +++ b/actix-macros/tests/trybuild.rs @@ -1,7 +1,10 @@ -#[rustversion::stable(1.46)] // MSRV +#![allow(missing_docs)] + +#[rustversion_msrv::msrv] #[test] fn compile_macros() { let t = trybuild::TestCases::new(); + t.pass("tests/trybuild/main-01-basic.rs"); t.compile_fail("tests/trybuild/main-02-only-async.rs"); t.pass("tests/trybuild/main-03-fn-params.rs"); diff --git a/actix-macros/tests/trybuild/main-02-only-async.stderr b/actix-macros/tests/trybuild/main-02-only-async.stderr index fc060071..2b15ecaa 100644 --- a/actix-macros/tests/trybuild/main-02-only-async.stderr +++ b/actix-macros/tests/trybuild/main-02-only-async.stderr @@ -1,14 +1,11 @@ error: the async keyword is missing from the function declaration - --> $DIR/main-02-only-async.rs:2:1 + --> tests/trybuild/main-02-only-async.rs:2:1 | 2 | fn main() { | ^^ error[E0601]: `main` function not found in crate `$CRATE` - --> $DIR/main-02-only-async.rs:1:1 + --> tests/trybuild/main-02-only-async.rs:4:2 | -1 | / #[actix_rt::main] -2 | | fn main() { -3 | | futures_util::future::ready(()).await -4 | | } - | |_^ consider adding a `main` function to `$DIR/tests/trybuild/main-02-only-async.rs` +4 | } + | ^ consider adding a `main` function to `$DIR/tests/trybuild/main-02-only-async.rs` diff --git a/actix-macros/tests/trybuild/test-04-system-path.rs b/actix-macros/tests/trybuild/test-04-system-path.rs index 6d0f9951..77479eaf 100644 --- a/actix-macros/tests/trybuild/test-04-system-path.rs +++ b/actix-macros/tests/trybuild/test-04-system-path.rs @@ -1,3 +1,5 @@ +#![allow(unused_imports)] + mod system { pub use actix_rt::System as MySystem; } diff --git a/actix-rt/CHANGES.md b/actix-rt/CHANGES.md index 50371c14..90822164 100644 --- a/actix-rt/CHANGES.md +++ b/actix-rt/CHANGES.md @@ -1,192 +1,182 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased +- Minimum supported Rust version (MSRV) is now 1.88. +- Add `SystemRunner::stop_future` and `SystemRunner::into_parts` for awaiting system stop inside `block_on`. +- Allow `{System, Arbiter}::with_tokio_rt` to accept shared Tokio runtimes (e.g. `Arc` or `&'static tokio::runtime::Runtime`). -## 2.6.0 - 2022-01-12 -- Update `tokio-uring` dependency to `0.2.0`. [#436] +## 2.11.0 -[#436]: https://github.com/actix/actix-net/pull/436 +- Implement `ActixStream` for `tokio::io::BufReader`. +- Deprecate the `pin` re-export. +- Minimum supported Rust version (MSRV) is now 1.75. +## 2.10.0 -## 2.5.1 - 2021-12-31 -- Expose `System::with_tokio_rt` and `Arbiter::with_tokio_rt`. [#430] +- Relax `F`'s bound (`Fn => FnOnce`) on `{Arbiter, System}::with_tokio_rt()` functions. +- Update `tokio-uring` dependency to `0.5`. +- Minimum supported Rust version (MSRV) is now 1.70. -[#430]: https://github.com/actix/actix-net/pull/430 +## 2.9.0 +- Add `actix_rt::System::runtime()` method to retrieve the underlying `actix_rt::Runtime` runtime. +- Add `actix_rt::Runtime::tokio_runtime()` method to retrieve the underlying Tokio runtime. +- Minimum supported Rust version (MSRV) is now 1.65. -## 2.5.0 - 2021-11-22 -- Add `System::run_with_code` to allow retrieving the exit code on stop. [#411] +## 2.8.0 -[#411]: https://github.com/actix/actix-net/pull/411 +- Add `#[track_caller]` attribute to `spawn` functions and methods. +- Update `tokio-uring` dependency to `0.4`. +- Minimum supported Rust version (MSRV) is now 1.59. +## 2.7.0 -## 2.4.0 - 2021-11-05 -- Add `Arbiter::try_current` for situations where thread may or may not have Arbiter context. [#408] -- Start io-uring with `System::new` when feature is enabled. [#395] +- Update `tokio-uring` dependency to `0.3`. +- Minimum supported Rust version (MSRV) is now 1.49. -[#395]: https://github.com/actix/actix-net/pull/395 -[#408]: https://github.com/actix/actix-net/pull/408 +## 2.6.0 +- Update `tokio-uring` dependency to `0.2`. -## 2.3.0 - 2021-10-11 -- The `spawn` method can now resolve with non-unit outputs. [#369] -- Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. [#374] +## 2.5.1 -[#369]: https://github.com/actix/actix-net/pull/369 -[#374]: https://github.com/actix/actix-net/pull/374 +- Expose `System::with_tokio_rt` and `Arbiter::with_tokio_rt`. +## 2.5.0 -## 2.2.0 - 2021-03-29 -- **BREAKING** `ActixStream::{poll_read_ready, poll_write_ready}` methods now return - `Ready` object in ok variant. [#293] - * Breakage is acceptable since `ActixStream` was not intended to be public. +- Add `System::run_with_code` to allow retrieving the exit code on stop. -[#293]: https://github.com/actix/actix-net/pull/293 +## 2.4.0 +- Add `Arbiter::try_current` for situations where thread may or may not have Arbiter context. +- Start io-uring with `System::new` when feature is enabled. -## 2.1.0 - 2021-02-24 -- Add `ActixStream` extension trait to include readiness methods. [#276] -- Re-export `tokio::net::TcpSocket` in `net` module [#282] +## 2.3.0 -[#276]: https://github.com/actix/actix-net/pull/276 -[#282]: https://github.com/actix/actix-net/pull/282 +- The `spawn` method can now resolve with non-unit outputs. +- Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. +## 2.2.0 -## 2.0.2 - 2021-02-06 -- Add `Arbiter::handle` to get a handle of an owned Arbiter. [#274] -- Add `System::try_current` for situations where actix may or may not be running a System. [#275] +- **BREAKING** `ActixStream::{poll_read_ready, poll_write_ready}` methods now return `Ready` object in ok variant. + - Breakage is acceptable since `ActixStream` was not intended to be public. -[#274]: https://github.com/actix/actix-net/pull/274 -[#275]: https://github.com/actix/actix-net/pull/275 +## 2.1.0 +- Add `ActixStream` extension trait to include readiness methods. +- Re-export `tokio::net::TcpSocket` in `net` module -## 2.0.1 - 2021-02-06 -- Expose `JoinError` from Tokio. [#271] +## 2.0.2 -[#271]: https://github.com/actix/actix-net/pull/271 +- Add `Arbiter::handle` to get a handle of an owned Arbiter. +- Add `System::try_current` for situations where actix may or may not be running a System. +## 2.0.1 -## 2.0.0 - 2021-02-02 -- Remove all Arbiter-local storage methods. [#262] -- Re-export `tokio::pin`. [#262] +- Expose `JoinError` from Tokio. -[#262]: https://github.com/actix/actix-net/pull/262 +## 2.0.0 +- Remove all Arbiter-local storage methods. +- Re-export `tokio::pin`. -## 2.0.0-beta.3 - 2021-01-31 -- Remove `run_in_tokio`, `attach_to_tokio` and `AsyncSystemRunner`. [#253] -- Return `JoinHandle` from `actix_rt::spawn`. [#253] -- Remove old `Arbiter::spawn`. Implementation is now inlined into `actix_rt::spawn`. [#253] -- Rename `Arbiter::{send => spawn}` and `Arbiter::{exec_fn => spawn_fn}`. [#253] -- Remove `Arbiter::exec`. [#253] -- Remove deprecated `Arbiter::local_join` and `Arbiter::is_running`. [#253] -- `Arbiter::spawn` now accepts !Unpin futures. [#256] -- `System::new` no longer takes arguments. [#257] -- Remove `System::with_current`. [#257] -- Remove `Builder`. [#257] -- Add `System::with_init` as replacement for `Builder::run`. [#257] -- Rename `System::{is_set => is_registered}`. [#257] -- Add `ArbiterHandle` for sending messages to non-current-thread arbiters. [#257]. -- `System::arbiter` now returns an `&ArbiterHandle`. [#257] -- `Arbiter::current` now returns an `ArbiterHandle` instead. [#257] -- `Arbiter::join` now takes self by value. [#257] +## 2.0.0-beta.3 -[#253]: https://github.com/actix/actix-net/pull/253 -[#254]: https://github.com/actix/actix-net/pull/254 -[#256]: https://github.com/actix/actix-net/pull/256 -[#257]: https://github.com/actix/actix-net/pull/257 +- Remove `run_in_tokio`, `attach_to_tokio` and `AsyncSystemRunner`. +- Return `JoinHandle` from `actix_rt::spawn`. +- Remove old `Arbiter::spawn`. Implementation is now inlined into `actix_rt::spawn`. +- Rename `Arbiter::{send => spawn}` and `Arbiter::{exec_fn => spawn_fn}`. +- Remove `Arbiter::exec`. +- Remove deprecated `Arbiter::local_join` and `Arbiter::is_running`. +- `Arbiter::spawn` now accepts !Unpin futures. +- `System::new` no longer takes arguments. +- Remove `System::with_current`. +- Remove `Builder`. +- Add `System::with_init` as replacement for `Builder::run`. +- Rename `System::{is_set => is_registered}`. +- Add `ArbiterHandle` for sending messages to non-current-thread arbiters. +- `System::arbiter` now returns an `&ArbiterHandle`. +- `Arbiter::current` now returns an `ArbiterHandle` instead. +- `Arbiter::join` now takes self by value. +## 2.0.0-beta.2 -## 2.0.0-beta.2 - 2021-01-09 -- Add `task` mod with re-export of `tokio::task::{spawn_blocking, yield_now, JoinHandle}` [#245] +- Add `task` mod with re-export of `tokio::task::{spawn_blocking, yield_now, JoinHandle}` - Add default "macros" feature to allow faster compile times when using `default-features=false`. -[#245]: https://github.com/actix/actix-net/pull/245 +## 2.0.0-beta.1 - -## 2.0.0-beta.1 - 2020-12-28 -- Add `System::attach_to_tokio` method. [#173] -- Update `tokio` dependency to `1.0`. [#236] -- Rename `time` module `delay_for` to `sleep`, `delay_until` to `sleep_until`, `Delay` to `Sleep` - to stay aligned with Tokio's naming. [#236] +- Add `System::attach_to_tokio` method. +- Update `tokio` dependency to `1.0`. +- Rename `time` module `delay_for` to `sleep`, `delay_until` to `sleep_until`, `Delay` to `Sleep` to stay aligned with Tokio's naming. - Remove `'static` lifetime requirement for `Runtime::block_on` and `SystemRunner::block_on`. - * These methods now accept `&self` when calling. [#236] -- Remove `'static` lifetime requirement for `System::run` and `Builder::run`. [#236] -- `Arbiter::spawn` now panics when `System` is not in scope. [#207] -- Fix work load issue by removing `PENDING` thread local. [#207] + - These methods now accept `&self` when calling. +- Remove `'static` lifetime requirement for `System::run` and `Builder::run`. +- `Arbiter::spawn` now panics when `System` is not in scope. +- Fix work load issue by removing `PENDING` thread local. -[#207]: https://github.com/actix/actix-net/pull/207 -[#236]: https://github.com/actix/actix-net/pull/236 +## 1.1.1 +- Fix memory leak due to -## 1.1.1 - 2020-04-30 -- Fix memory leak due to [#94] (see [#129] for more detail) +## 1.1.0 _(YANKED)_ -[#129]: https://github.com/actix/actix-net/issues/129 +- Expose `System::is_set` to check if current system has ben started +- Add `Arbiter::is_running` to check if event loop is running +- Add `Arbiter::local_join` associated function to get be able to `await` for spawned futures +## 1.0.0 -## 1.1.0 - 2020-04-08 _(YANKED)_ -- Expose `System::is_set` to check if current system has ben started [#99] -- Add `Arbiter::is_running` to check if event loop is running [#124] -- Add `Arbiter::local_join` associated function - to get be able to `await` for spawned futures [#94] - -[#94]: https://github.com/actix/actix-net/pull/94 -[#99]: https://github.com/actix/actix-net/pull/99 -[#124]: https://github.com/actix/actix-net/pull/124 - - -## 1.0.0 - 2019-12-11 - Update dependencies +## 1.0.0-alpha.3 -## 1.0.0-alpha.3 - 2019-12-07 - Migrate to tokio 0.2 - Fix compilation on non-unix platforms +## 1.0.0-alpha.2 -## 1.0.0-alpha.2 - 2019-12-02 - Export `main` and `test` attribute macros - Export `time` module (re-export of tokio-timer) - Export `net` module (re-export of tokio-net) +## 1.0.0-alpha.1 -## 1.0.0-alpha.1 - 2019-11-22 - Migrate to std::future and tokio 0.2 +## 0.2.6 -## 0.2.6 - 2019-11-14 - Allow to join arbiter's thread. #60 - Fix arbiter's thread panic message. +## 0.2.5 -## 0.2.5 - 2019-09-02 - Add arbiter specific storage +## 0.2.4 -## 0.2.4 - 2019-07-17 - Avoid a copy of the Future when initializing the Box. #29 +## 0.2.3 -## 0.2.3 - 2019-06-22 - Allow to start System using existing CurrentThread Handle #22 +## 0.2.2 -## 0.2.2 - 2019-03-28 - Moved `blocking` module to `actix-threadpool` crate +## 0.2.1 -## 0.2.1 - 2019-03-11 - Added `blocking` module - Added `Arbiter::exec_fn` - execute fn on the arbiter's thread - Added `Arbiter::exec` - execute fn on the arbiter's thread and wait result +## 0.2.0 -## 0.2.0 - 2019-03-06 - `run` method returns `io::Result<()>` - Removed `Handle` +## 0.1.0 -## 0.1.0 - 2018-12-09 - Initial release diff --git a/actix-rt/Cargo.toml b/actix-rt/Cargo.toml index 406c1de5..1d1ff329 100644 --- a/actix-rt/Cargo.toml +++ b/actix-rt/Cargo.toml @@ -1,22 +1,18 @@ [package] name = "actix-rt" -version = "2.6.0" -authors = [ - "Nikolay Kim ", - "Rob Ede ", - "fakeshadow <24548779@qq.com>", -] +version = "2.11.0" +authors = ["Nikolay Kim ", "Rob Ede "] description = "Tokio-based single-threaded async runtime for the Actix ecosystem" -keywords = ["async", "futures", "io", "runtime"] -homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-net.git" categories = ["network-programming", "asynchronous"] -license = "MIT OR Apache-2.0" -edition = "2018" +keywords = ["async", "futures", "io", "runtime"] +homepage.workspace = true +repository.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true -[lib] -name = "actix_rt" -path = "src/lib.rs" +[package.metadata.cargo_check_external_types] +allowed_external_types = ["actix_macros::*", "tokio::*"] [features] default = ["macros"] @@ -27,12 +23,14 @@ io-uring = ["tokio-uring"] actix-macros = { version = "0.2.3", optional = true } futures-core = { version = "0.3", default-features = false } -tokio = { version = "1.13.1", features = ["rt", "net", "parking_lot", "signal", "sync", "time"] } +tokio = { version = "1.44.2", features = ["rt", "io-util", "net", "parking_lot", "signal", "sync", "time"] } -# runtime for io-uring feature +# runtime for `io-uring` feature [target.'cfg(target_os = "linux")'.dependencies] -tokio-uring = { version = "0.2", optional = true } +tokio-uring = { version = "0.5", optional = true } [dev-dependencies] -tokio = { version = "1.13.1", features = ["full"] } -hyper = { version = "0.14.10", default-features = false, features = ["server", "tcp", "http1"] } +tokio = { version = "1.44.2", features = ["full"] } + +[lints] +workspace = true diff --git a/actix-rt/README.md b/actix-rt/README.md index 5e912032..31037720 100644 --- a/actix-rt/README.md +++ b/actix-rt/README.md @@ -3,11 +3,11 @@ > Tokio-based single-threaded async runtime for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-rt?label=latest)](https://crates.io/crates/actix-rt) -[![Documentation](https://docs.rs/actix-rt/badge.svg?version=2.6.0)](https://docs.rs/actix-rt/2.6.0) -[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) +[![Documentation](https://docs.rs/actix-rt/badge.svg?version=2.11.0)](https://docs.rs/actix-rt/2.11.0) +[![Version](https://img.shields.io/badge/rustc-1.88+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-rt.svg)
-[![dependency status](https://deps.rs/crate/actix-rt/2.6.0/status.svg)](https://deps.rs/crate/actix-rt/2.6.0) +[![dependency status](https://deps.rs/crate/actix-rt/2.11.0/status.svg)](https://deps.rs/crate/actix-rt/2.11.0) ![Download](https://img.shields.io/crates/d/actix-rt.svg) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/WghFtEH6Hb) diff --git a/actix-rt/examples/hyper.rs b/actix-rt/examples/hyper.rs deleted file mode 100644 index 3520acd0..00000000 --- a/actix-rt/examples/hyper.rs +++ /dev/null @@ -1,28 +0,0 @@ -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Request, Response, Server}; -use std::convert::Infallible; -use std::net::SocketAddr; - -async fn handle(_req: Request) -> Result, Infallible> { - Ok(Response::new(Body::from("Hello World"))) -} - -fn main() { - actix_rt::System::with_tokio_rt(|| { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - }) - .block_on(async { - let make_service = - make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(handle)) }); - - let server = - Server::bind(&SocketAddr::from(([127, 0, 0, 1], 3000))).serve(make_service); - - if let Err(err) = server.await { - eprintln!("server error: {}", err); - } - }) -} diff --git a/actix-rt/src/arbiter.rs b/actix-rt/src/arbiter.rs index 95256360..80ba055b 100644 --- a/actix-rt/src/arbiter.rs +++ b/actix-rt/src/arbiter.rs @@ -16,7 +16,7 @@ use crate::system::{System, SystemCommand}; pub(crate) static COUNT: AtomicUsize = AtomicUsize::new(0); thread_local!( - static HANDLE: RefCell> = RefCell::new(None); + static HANDLE: RefCell> = const { RefCell::new(None) }; ); pub(crate) enum ArbiterCommand { @@ -99,24 +99,29 @@ impl Arbiter { #[allow(clippy::new_without_default)] pub fn new() -> Arbiter { Self::with_tokio_rt(|| { - crate::runtime::default_tokio_runtime() - .expect("Cannot create new Arbiter's Runtime.") + crate::runtime::default_tokio_runtime().expect("Cannot create new Arbiter's Runtime.") }) } /// Spawn a new Arbiter using the [Tokio Runtime](tokio-runtime) returned from a closure. /// + /// The closure may return any type that can be converted into [`Runtime`], such as + /// `tokio::runtime::Runtime`, `Arc`, or + /// `&'static tokio::runtime::Runtime`. + /// /// [tokio-runtime]: tokio::runtime::Runtime + /// [`Runtime`]: crate::Runtime #[cfg(not(all(target_os = "linux", feature = "io-uring")))] - pub fn with_tokio_rt(runtime_factory: F) -> Arbiter + pub fn with_tokio_rt(runtime_factory: F) -> Arbiter where - F: Fn() -> tokio::runtime::Runtime + Send + 'static, + F: FnOnce() -> R + Send + 'static, + R: Into + Send + 'static, { let sys = System::current(); let system_id = sys.id(); let arb_id = COUNT.fetch_add(1, Ordering::Relaxed); - let name = format!("actix-rt|system:{}|arbiter:{}", system_id, arb_id); + let name = format!("actix-rt|system:{system_id}|arbiter:{arb_id}"); let (tx, rx) = mpsc::unbounded_channel(); let (ready_tx, ready_rx) = std::sync::mpsc::channel::<()>(); @@ -126,7 +131,7 @@ impl Arbiter { .spawn({ let tx = tx.clone(); move || { - let rt = crate::runtime::Runtime::from(runtime_factory()); + let rt = runtime_factory().into(); let hnd = ArbiterHandle::new(tx); System::set_current(sys); @@ -149,9 +154,7 @@ impl Arbiter { .send(SystemCommand::DeregisterArbiter(arb_id)); } }) - .unwrap_or_else(|err| { - panic!("Cannot spawn Arbiter's thread: {:?}. {:?}", &name, err) - }); + .unwrap_or_else(|err| panic!("Cannot spawn Arbiter's thread: {name:?}: {err:?}")); ready_rx.recv().unwrap(); @@ -201,9 +204,7 @@ impl Arbiter { .send(SystemCommand::DeregisterArbiter(arb_id)); } }) - .unwrap_or_else(|err| { - panic!("Cannot spawn Arbiter's thread: {:?}. {:?}", &name, err) - }); + .unwrap_or_else(|err| panic!("Cannot spawn Arbiter's thread: {name:?}: {err:?}")); ready_rx.recv().unwrap(); @@ -260,6 +261,7 @@ impl Arbiter { /// If you require a result, include a response channel in the future. /// /// Returns true if future was sent successfully and false if the Arbiter has died. + #[track_caller] pub fn spawn(&self, future: Fut) -> bool where Fut: Future + Send + 'static, @@ -275,6 +277,7 @@ impl Arbiter { /// channel in the function. /// /// Returns true if function was sent successfully and false if the Arbiter has died. + #[track_caller] pub fn spawn_fn(&self, f: F) -> bool where F: FnOnce() + Send + 'static, @@ -301,7 +304,7 @@ impl Future for ArbiterRunner { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { // process all items currently buffered in channel loop { - match ready!(Pin::new(&mut self.rx).poll_recv(cx)) { + match ready!(self.rx.poll_recv(cx)) { // channel closed; no more messages can be received None => return Poll::Ready(()), diff --git a/actix-rt/src/lib.rs b/actix-rt/src/lib.rs index 7fb2b632..ec2176d3 100644 --- a/actix-rt/src/lib.rs +++ b/actix-rt/src/lib.rs @@ -34,14 +34,13 @@ //! ``` //! //! # `io-uring` Support +//! //! There is experimental support for using io-uring with this crate by enabling the //! `io-uring` feature. For now, it is semver exempt. //! //! Note that there are currently some unimplemented parts of using `actix-rt` with `io-uring`. //! In particular, when running a `System`, only `System::block_on` is supported. -#![deny(rust_2018_idioms, nonstandard_style)] -#![warn(future_incompatible, missing_docs)] #![allow(clippy::type_complexity)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] @@ -51,13 +50,10 @@ compile_error!("io_uring is a linux only feature."); use std::future::Future; -use tokio::task::JoinHandle; - // Cannot define a main macro when compiled into test harness. // Workaround for https://github.com/rust-lang/rust/issues/62127. #[cfg(all(feature = "macros", not(test)))] pub use actix_macros::main; - #[cfg(feature = "macros")] pub use actix_macros::test; @@ -65,11 +61,15 @@ mod arbiter; mod runtime; mod system; -pub use self::arbiter::{Arbiter, ArbiterHandle}; -pub use self::runtime::Runtime; -pub use self::system::{System, SystemRunner}; - +#[deprecated(since = "2.11.0", note = "Prefer `std::pin::pin!`.")] pub use tokio::pin; +use tokio::task::JoinHandle; + +pub use self::{ + arbiter::{Arbiter, ArbiterHandle}, + runtime::Runtime, + system::{System, SystemRunner, SystemStop}, +}; pub mod signal { //! Asynchronous signal handling (Tokio re-exports). @@ -88,16 +88,17 @@ pub mod net { use std::{ future::Future, io, + pin::pin, task::{Context, Poll}, }; - pub use tokio::io::Ready; - use tokio::io::{AsyncRead, AsyncWrite, Interest}; - pub use tokio::net::UdpSocket; - pub use tokio::net::{TcpListener, TcpSocket, TcpStream}; - + use tokio::io::{AsyncRead, AsyncWrite, BufReader, Interest}; #[cfg(unix)] pub use tokio::net::{UnixDatagram, UnixListener, UnixStream}; + pub use tokio::{ + io::Ready, + net::{TcpListener, TcpSocket, TcpStream, UdpSocket}, + }; /// Extension trait over async read+write types that can also signal readiness. #[doc(hidden)] @@ -116,14 +117,12 @@ pub mod net { impl ActixStream for TcpStream { fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll> { let ready = self.ready(Interest::READABLE); - tokio::pin!(ready); - ready.poll(cx) + pin!(ready).poll(cx) } fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll> { let ready = self.ready(Interest::WRITABLE); - tokio::pin!(ready); - ready.poll(cx) + pin!(ready).poll(cx) } } @@ -131,14 +130,12 @@ pub mod net { impl ActixStream for UnixStream { fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll> { let ready = self.ready(Interest::READABLE); - tokio::pin!(ready); - ready.poll(cx) + pin!(ready).poll(cx) } fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll> { let ready = self.ready(Interest::WRITABLE); - tokio::pin!(ready); - ready.poll(cx) + pin!(ready).poll(cx) } } @@ -151,15 +148,24 @@ pub mod net { (**self).poll_write_ready(cx) } } + + impl ActixStream for BufReader { + fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll> { + self.get_ref().poll_read_ready(cx) + } + + fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll> { + self.get_ref().poll_write_ready(cx) + } + } } pub mod time { //! Utilities for tracking time (Tokio re-exports). - pub use tokio::time::Instant; - pub use tokio::time::{interval, interval_at, Interval}; - pub use tokio::time::{sleep, sleep_until, Sleep}; - pub use tokio::time::{timeout, Timeout}; + pub use tokio::time::{ + interval, interval_at, sleep, sleep_until, timeout, Instant, Interval, Sleep, Timeout, + }; } pub mod task { @@ -198,6 +204,7 @@ pub mod task { /// assert!(handle.await.unwrap_err().is_cancelled()); /// # }); /// ``` +#[track_caller] #[inline] pub fn spawn(f: Fut) -> JoinHandle where diff --git a/actix-rt/src/runtime.rs b/actix-rt/src/runtime.rs index 25937003..408853c8 100644 --- a/actix-rt/src/runtime.rs +++ b/actix-rt/src/runtime.rs @@ -1,7 +1,14 @@ -use std::{future::Future, io}; +use std::{future::Future, io, sync::Arc}; use tokio::task::{JoinHandle, LocalSet}; +#[derive(Debug)] +enum RuntimeInner { + Owned(tokio::runtime::Runtime), + Shared(Arc), + Static(&'static tokio::runtime::Runtime), +} + /// A Tokio-based runtime proxy. /// /// All spawned futures will be executed on the current thread. Therefore, there is no `Send` bound @@ -9,7 +16,7 @@ use tokio::task::{JoinHandle, LocalSet}; #[derive(Debug)] pub struct Runtime { local: LocalSet, - rt: tokio::runtime::Runtime, + rt: RuntimeInner, } pub(crate) fn default_tokio_runtime() -> io::Result { @@ -26,11 +33,19 @@ impl Runtime { let rt = default_tokio_runtime()?; Ok(Runtime { - rt, + rt: RuntimeInner::Owned(rt), local: LocalSet::new(), }) } + fn tokio_runtime_ref(&self) -> &tokio::runtime::Runtime { + match &self.rt { + RuntimeInner::Owned(rt) => rt, + RuntimeInner::Shared(rt) => rt, + RuntimeInner::Static(rt) => rt, + } + } + /// Offload a future onto the single-threaded runtime. /// /// The returned join handle can be used to await the future's result. @@ -53,6 +68,7 @@ impl Runtime { /// # Panics /// This function panics if the spawn fails. Failure occurs if the executor is currently at /// capacity and is unable to spawn a new future. + #[track_caller] pub fn spawn(&self, future: F) -> JoinHandle where F: Future + 'static, @@ -60,6 +76,62 @@ impl Runtime { self.local.spawn_local(future) } + /// Retrieves a reference to the underlying Tokio runtime associated with this instance. + /// + /// The Tokio runtime is responsible for executing asynchronous tasks and managing + /// the event loop for an asynchronous Rust program. This method allows accessing + /// the runtime to interact with its features directly. + /// + /// In a typical use case, you might need to share the same runtime between different + /// modules of your project. For example, a module might require a `tokio::runtime::Handle` + /// to spawn tasks on the same runtime, or the runtime itself to configure more complex + /// behaviours. + /// + /// # Example + /// + /// ``` + /// use actix_rt::Runtime; + /// + /// mod module_a { + /// pub fn do_something(handle: tokio::runtime::Handle) { + /// handle.spawn(async { + /// // Some asynchronous task here + /// }); + /// } + /// } + /// + /// mod module_b { + /// pub fn do_something_else(rt: &tokio::runtime::Runtime) { + /// rt.spawn(async { + /// // Another asynchronous task here + /// }); + /// } + /// } + /// + /// let actix_runtime = actix_rt::Runtime::new().unwrap(); + /// let tokio_runtime = actix_runtime.tokio_runtime(); + /// + /// let handle = tokio_runtime.handle().clone(); + /// + /// module_a::do_something(handle); + /// module_b::do_something_else(tokio_runtime); + /// ``` + /// + /// # Returns + /// + /// An immutable reference to the `tokio::runtime::Runtime` instance associated with this + /// `Runtime` instance. + /// + /// # Note + /// + /// While this method provides an immutable reference to the Tokio runtime, which is safe to share across threads, + /// be aware that spawning blocking tasks on the Tokio runtime could potentially impact the execution + /// of the Actix runtime. This is because Tokio is responsible for driving the Actix system, + /// and blocking tasks could delay or deadlock other tasks in run loop. + pub fn tokio_runtime(&self) -> &tokio::runtime::Runtime { + self.tokio_runtime_ref() + } + /// Runs the provided future, blocking the current thread until the future completes. /// /// This function can be used to synchronously block the current thread until the provided @@ -73,11 +145,12 @@ impl Runtime { /// /// The caller is responsible for ensuring that other spawned futures complete execution by /// calling `block_on` or `run`. + #[track_caller] pub fn block_on(&self, f: F) -> F::Output where F: Future, { - self.local.block_on(&self.rt, f) + self.local.block_on(self.tokio_runtime_ref(), f) } } @@ -85,7 +158,25 @@ impl From for Runtime { fn from(rt: tokio::runtime::Runtime) -> Self { Self { local: LocalSet::new(), - rt, + rt: RuntimeInner::Owned(rt), + } + } +} + +impl From> for Runtime { + fn from(rt: Arc) -> Self { + Self { + local: LocalSet::new(), + rt: RuntimeInner::Shared(rt), + } + } +} + +impl From<&'static tokio::runtime::Runtime> for Runtime { + fn from(rt: &'static tokio::runtime::Runtime) -> Self { + Self { + local: LocalSet::new(), + rt: RuntimeInner::Static(rt), } } } diff --git a/actix-rt/src/system.rs b/actix-rt/src/system.rs index 0ea3547d..76f29df5 100644 --- a/actix-rt/src/system.rs +++ b/actix-rt/src/system.rs @@ -9,14 +9,14 @@ use std::{ }; use futures_core::ready; -use tokio::sync::{mpsc, oneshot}; +use tokio::sync::{mpsc, watch}; use crate::{arbiter::ArbiterHandle, Arbiter}; static SYSTEM_COUNT: AtomicUsize = AtomicUsize::new(0); thread_local!( - static CURRENT: RefCell> = RefCell::new(None); + static CURRENT: RefCell> = const { RefCell::new(None) }; ); /// A manager for a per-thread distributed async runtime. @@ -45,15 +45,21 @@ impl System { /// Create a new System using the [Tokio Runtime](tokio-runtime) returned from a closure. /// + /// The closure may return any type that can be converted into [`Runtime`], such as + /// `tokio::runtime::Runtime`, `Arc`, or + /// `&'static tokio::runtime::Runtime`. + /// /// [tokio-runtime]: tokio::runtime::Runtime - pub fn with_tokio_rt(runtime_factory: F) -> SystemRunner + /// [`Runtime`]: crate::Runtime + pub fn with_tokio_rt(runtime_factory: F) -> SystemRunner where - F: Fn() -> tokio::runtime::Runtime, + F: FnOnce() -> R, + R: Into, { - let (stop_tx, stop_rx) = oneshot::channel(); + let (stop_tx, stop_rx) = watch::channel(None); let (sys_tx, sys_rx) = mpsc::unbounded_channel(); - let rt = crate::runtime::Runtime::from(runtime_factory()); + let rt = runtime_factory().into(); let sys_arbiter = rt.block_on(async { Arbiter::in_new_system() }); let system = System::construct(sys_tx, sys_arbiter.clone()); @@ -85,9 +91,10 @@ impl System { /// /// [tokio-runtime]: tokio::runtime::Runtime #[doc(hidden)] - pub fn with_tokio_rt(_: F) -> SystemRunner + pub fn with_tokio_rt(_: F) -> SystemRunner where - F: Fn() -> tokio::runtime::Runtime, + F: FnOnce() -> R, + R: Into, { unimplemented!("System::with_tokio_rt is not implemented for io-uring feature yet") } @@ -176,7 +183,7 @@ impl System { #[derive(Debug)] pub struct SystemRunner { rt: crate::runtime::Runtime, - stop_rx: oneshot::Receiver, + stop_rx: watch::Receiver>, } #[cfg(not(feature = "io-uring"))] @@ -187,10 +194,7 @@ impl SystemRunner { match exit_code { 0 => Ok(()), - nonzero => Err(io::Error::new( - io::ErrorKind::Other, - format!("Non-zero exit code: {}", nonzero), - )), + nonzero => Err(io::Error::other(format!("Non-zero exit code: {nonzero}"))), } } @@ -199,11 +203,82 @@ impl SystemRunner { let SystemRunner { rt, stop_rx, .. } = self; // run loop - rt.block_on(stop_rx) - .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) + rt.block_on(wait_for_stop(stop_rx)) + } + + /// Retrieves a reference to the underlying [Actix runtime](crate::Runtime) associated with this + /// `SystemRunner` instance. + /// + /// The Actix runtime is responsible for managing the event loop for an Actix system and + /// executing asynchronous tasks. This method provides access to the runtime, allowing direct + /// interaction with its features. + /// + /// In a typical use case, you might need to share the same runtime between different + /// parts of your project. For example, some components might require a [`Runtime`] to spawn + /// tasks on the same runtime. + /// + /// Read more in the documentation for [`Runtime`]. + /// + /// # Examples + /// + /// ``` + /// let system_runner = actix_rt::System::new(); + /// let actix_runtime = system_runner.runtime(); + /// + /// // Use the runtime to spawn an async task or perform other operations + /// ``` + /// + /// # Note + /// + /// While this method provides an immutable reference to the Actix runtime, which is safe to + /// share across threads, be aware that spawning blocking tasks on the Actix runtime could + /// potentially impact system performance. This is because the Actix runtime is responsible for + /// driving the system, and blocking tasks could delay other tasks in the run loop. + /// + /// [`Runtime`]: crate::Runtime + pub fn runtime(&self) -> &crate::runtime::Runtime { + &self.rt + } + + /// Returns a future that resolves with the system's exit code when it is stopped. + /// + /// This can be used to react to a system stop signal while running a future with + /// [`SystemRunner::block_on`], such as when coordinating shutdown with `tokio::select!`. + /// + /// # Examples + /// ```no_run + /// use std::process::ExitCode; + /// use actix_rt::System; + /// + /// let sys = System::new(); + /// let stop = sys.stop_future(); + /// + /// let exit = sys.block_on(async move { + /// actix_rt::spawn(async { + /// System::current().stop_with_code(0); + /// }); + /// + /// let code = stop.await.unwrap_or(1); + /// ExitCode::from(code as u8) + /// }); + /// + /// # drop(exit); + /// ``` + pub fn stop_future(&self) -> SystemStop { + SystemStop::new(self.stop_rx.clone()) + } + + /// Splits this runner into its runtime and a future that resolves when the system stops. + /// + /// After calling this method, [`SystemRunner::run`] and [`SystemRunner::run_with_code`] can no + /// longer be used. + pub fn into_parts(self) -> (crate::runtime::Runtime, SystemStop) { + let SystemRunner { rt, stop_rx } = self; + (rt, SystemStop::new(stop_rx)) } /// Runs the provided future, blocking the current thread until the future completes. + #[track_caller] #[inline] pub fn block_on(&self, fut: F) -> F::Output { self.rt.block_on(fut) @@ -225,16 +300,24 @@ impl SystemRunner { /// Runs the event loop until [stopped](System::stop_with_code), returning the exit code. pub fn run_with_code(self) -> io::Result { - unimplemented!( - "SystemRunner::run_with_code is not implemented for io-uring feature yet" - ); + unimplemented!("SystemRunner::run_with_code is not implemented for io-uring feature yet"); + } + + /// Returns a future that resolves with the system's exit code when it is stopped. + pub fn stop_future(&self) -> SystemStop { + unimplemented!("SystemRunner::stop_future is not implemented for io-uring feature yet"); + } + + /// Splits this runner into its runtime and a future that resolves when the system stops. + pub fn into_parts(self) -> (crate::runtime::Runtime, SystemStop) { + unimplemented!("SystemRunner::into_parts is not implemented for io-uring feature yet"); } /// Runs the provided future, blocking the current thread until the future completes. #[inline] pub fn block_on(&self, fut: F) -> F::Output { tokio_uring::start(async move { - let (stop_tx, stop_rx) = oneshot::channel(); + let (stop_tx, stop_rx) = watch::channel(None); let (sys_tx, sys_rx) = mpsc::unbounded_channel(); let sys_arbiter = Arbiter::in_new_system(); @@ -256,6 +339,40 @@ impl SystemRunner { } } +/// Future that resolves with the exit code when a [`System`] is stopped. +#[must_use = "SystemStop does nothing unless polled or awaited."] +pub struct SystemStop { + inner: Pin> + 'static>>, +} + +impl SystemStop { + #[cfg_attr(feature = "io-uring", allow(dead_code))] + fn new(stop_rx: watch::Receiver>) -> Self { + Self { + inner: Box::pin(wait_for_stop(stop_rx)), + } + } +} + +impl Future for SystemStop { + type Output = io::Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.inner.as_mut().poll(cx) + } +} + +#[cfg_attr(feature = "io-uring", allow(dead_code))] +async fn wait_for_stop(mut stop_rx: watch::Receiver>) -> io::Result { + loop { + if let Some(code) = *stop_rx.borrow() { + return Ok(code); + } + + stop_rx.changed().await.map_err(io::Error::other)?; + } +} + #[derive(Debug)] pub(crate) enum SystemCommand { Exit(i32), @@ -267,7 +384,7 @@ pub(crate) enum SystemCommand { /// [Arbiter]s and is able to distribute a system-wide stop command. #[derive(Debug)] pub(crate) struct SystemController { - stop_tx: Option>, + stop_tx: Option>>, cmd_rx: mpsc::UnboundedReceiver, arbiters: HashMap, } @@ -275,7 +392,7 @@ pub(crate) struct SystemController { impl SystemController { pub(crate) fn new( cmd_rx: mpsc::UnboundedReceiver, - stop_tx: oneshot::Sender, + stop_tx: watch::Sender>, ) -> Self { SystemController { cmd_rx, @@ -291,7 +408,7 @@ impl Future for SystemController { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { // process all items currently buffered in channel loop { - match ready!(Pin::new(&mut self.cmd_rx).poll_recv(cx)) { + match ready!(self.cmd_rx.poll_recv(cx)) { // channel closed; no more messages can be received None => return Poll::Ready(()), @@ -306,7 +423,7 @@ impl Future for SystemController { // stop event loop // will only fire once if let Some(stop_tx) = self.stop_tx.take() { - let _ = stop_tx.send(code); + let _ = stop_tx.send(Some(code)); } } diff --git a/actix-rt/tests/tests.rs b/actix-rt/tests/tests.rs index 551a395d..1110a055 100644 --- a/actix-rt/tests/tests.rs +++ b/actix-rt/tests/tests.rs @@ -1,10 +1,11 @@ +#![allow(missing_docs)] + use std::{ future::Future, time::{Duration, Instant}, }; use actix_rt::{task::JoinError, Arbiter, System}; - #[cfg(not(feature = "io-uring"))] use { std::{sync::mpsc::channel, thread}, @@ -33,6 +34,34 @@ fn run_with_code() { assert_eq!(exit_code, 42); } +#[cfg(not(feature = "io-uring"))] +#[test] +fn stop_future_resolves() { + let sys = System::new(); + let stop = sys.stop_future(); + + let exit_code = sys.block_on(async move { + System::current().stop_with_code(7); + stop.await.expect("stop future should resolve") + }); + + assert_eq!(exit_code, 7); +} + +#[cfg(not(feature = "io-uring"))] +#[test] +fn into_parts_stop_future_resolves() { + let sys = System::new(); + let (rt, stop) = sys.into_parts(); + + let exit_code = rt.block_on(async move { + System::current().stop_with_code(9); + stop.await.expect("stop future should resolve") + }); + + assert_eq!(exit_code, 9); +} + #[test] fn join_another_arbiter() { let time = Duration::from_secs(1); @@ -269,6 +298,65 @@ fn new_system_with_tokio() { assert_eq!(rx.recv().unwrap(), 42); } +#[cfg(not(feature = "io-uring"))] +#[test] +fn new_system_with_shared_tokio_runtime() { + use std::sync::Arc; + + let (tx, rx) = channel(); + + let rt = Arc::new( + tokio::runtime::Builder::new_multi_thread() + .enable_io() + .enable_time() + .worker_threads(2) + .max_blocking_threads(2) + .build() + .unwrap(), + ); + + let res = System::with_tokio_rt({ + let rt = rt.clone(); + move || rt + }) + .block_on(async { + actix_rt::time::sleep(Duration::from_millis(1)).await; + + tokio::task::spawn(async move { + tx.send(7).unwrap(); + }) + .await + .unwrap(); + + 321usize + }); + + assert_eq!(res, 321); + assert_eq!(rx.recv().unwrap(), 7); +} + +#[cfg(not(feature = "io-uring"))] +#[test] +fn new_system_with_static_tokio_runtime() { + use std::sync::OnceLock; + + static TOKIO: OnceLock = OnceLock::new(); + + let res = System::with_tokio_rt(|| -> &'static tokio::runtime::Runtime { + TOKIO.get_or_init(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_io() + .enable_time() + .worker_threads(1) + .build() + .unwrap() + }) + }) + .block_on(async { 7usize }); + + assert_eq!(res, 7); +} + #[cfg(not(feature = "io-uring"))] #[test] fn new_arbiter_with_tokio() { @@ -302,6 +390,45 @@ fn new_arbiter_with_tokio() { assert!(!counter.load(Ordering::SeqCst)); } +#[cfg(not(feature = "io-uring"))] +#[test] +fn new_arbiter_with_shared_tokio_runtime() { + use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }; + + let _ = System::new(); + + let rt = Arc::new( + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .worker_threads(2) + .build() + .unwrap(), + ); + + let arb = Arbiter::with_tokio_rt({ + let rt = rt.clone(); + move || rt + }); + + let flag = Arc::new(AtomicBool::new(false)); + + let flag1 = flag.clone(); + let did_spawn = arb.spawn(async move { + actix_rt::time::sleep(Duration::from_millis(1)).await; + flag1.store(true, Ordering::SeqCst); + Arbiter::current().stop(); + }); + + assert!(did_spawn); + + arb.join().unwrap(); + + assert!(flag.load(Ordering::SeqCst)); +} + #[test] #[should_panic] fn no_system_current_panic() { @@ -359,7 +486,7 @@ fn tokio_uring_arbiter() { let f = tokio_uring::fs::File::create("test.txt").await.unwrap(); let buf = b"Hello World!"; - let (res, _) = f.write_at(&buf[..], 0).await; + let (res, _) = f.write_all_at(&buf[..], 0).await; assert!(res.is_ok()); f.sync_all().await.unwrap(); diff --git a/actix-server/CHANGES.md b/actix-server/CHANGES.md index 14e5f4d7..aff4c8da 100644 --- a/actix-server/CHANGES.md +++ b/actix-server/CHANGES.md @@ -1,241 +1,240 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased +- Minimum supported Rust version (MSRV) is now 1.88. +- internal: Use `core::future::{ready, Ready}` instead of actix-utils' + +## 2.6.0 + +- Add `ServerBuilder::shutdown_signal()` method. +- Minimum supported Rust version (MSRV) is now 1.75. + +## 2.5.1 + +- Fix panic in test server. +- Minimum supported Rust version (MSRV) is now 1.71. + +## 2.5.0 + +- Update `mio` dependency to `1`. + +## 2.4.0 + +- Update `tokio-uring` dependency to `0.5`. +- Minimum supported Rust version (MSRV) is now 1.70. + +## 2.3.0 + +- Add support for MultiPath TCP (MPTCP) with `MpTcp` enum and `ServerBuilder::mptcp()` method. +- Minimum supported Rust version (MSRV) is now 1.65. + +## 2.2.0 + +- Minimum supported Rust version (MSRV) is now 1.59. +- Update `tokio-uring` dependency to `0.4`. + +## 2.1.1 + +- No significant changes since `2.1.0`. + +## 2.1.0 + +- Update `tokio-uring` dependency to `0.3`. +- Logs emitted now use the `tracing` crate with `log` compatibility. +- Wait for accept thread to stop before sending completion signal. + +## 2.0.0 -## 2.0.0 - 2022-01-19 - No significant changes since `2.0.0-rc.4`. +## 2.0.0-rc.4 -## 2.0.0-rc.4 - 2022-01-12 -- Update `tokio-uring` dependency to `0.2.0`. [#436] +- Update `tokio-uring` dependency to `0.2`. -[#436]: https://github.com/actix/actix-net/pull/436 +## 2.0.0-rc.3 - -## 2.0.0-rc.3 - 2021-12-31 - No significant changes since `2.0.0-rc.2`. +## 2.0.0-rc.2 -## 2.0.0-rc.2 - 2021-12-27 -- Simplify `TestServer`. [#431] +- Simplify `TestServer`. -[#431]: https://github.com/actix/actix-net/pull/431 +## 2.0.0-rc.1 +- Hide implementation details of `Server`. +- `Server` now runs only after awaiting it. -## 2.0.0-rc.1 - 2021-12-05 -- Hide implementation details of `Server`. [#424] -- `Server` now runs only after awaiting it. [#425] +## 2.0.0-beta.9 -[#424]: https://github.com/actix/actix-net/pull/424 -[#425]: https://github.com/actix/actix-net/pull/425 +- Restore `Arbiter` support lost in `beta.8`. +## 2.0.0-beta.8 -## 2.0.0-beta.9 - 2021-11-15 -- Restore `Arbiter` support lost in `beta.8`. [#417] +- Fix non-unix signal handler. -[#417]: https://github.com/actix/actix-net/pull/417 +## 2.0.0-beta.7 - -## 2.0.0-beta.8 - 2021-11-05 _(YANKED)_ -- Fix non-unix signal handler. [#410] - -[#410]: https://github.com/actix/actix-net/pull/410 - - -## 2.0.0-beta.7 - 2021-11-05 _(YANKED)_ -- Server can be started in regular Tokio runtime. [#408] -- Expose new `Server` type whose `Future` impl resolves when server stops. [#408] -- Rename `Server` to `ServerHandle`. [#407] -- Add `Server::handle` to obtain handle to server. [#408] -- Rename `ServerBuilder::{maxconn => max_concurrent_connections}`. [#407] -- Deprecate crate-level `new` shortcut for server builder. [#408] +- Server can be started in regular Tokio runtime. +- Expose new `Server` type whose `Future` impl resolves when server stops. +- Rename `Server` to `ServerHandle`. +- Add `Server::handle` to obtain handle to server. +- Rename `ServerBuilder::{maxconn => max_concurrent_connections}`. +- Deprecate crate-level `new` shortcut for server builder. - Minimum supported Rust version (MSRV) is now 1.52. -[#407]: https://github.com/actix/actix-net/pull/407 -[#408]: https://github.com/actix/actix-net/pull/408 +## 2.0.0-beta.6 +- Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. +- Server no long listens to `SIGHUP` signal. Previously, the received was not used but did block subsequent exit signals from working. +- Remove `config` module. `ServiceConfig`, `ServiceRuntime` public types are removed due to this change. +- Remove `ServerBuilder::configure`. -## 2.0.0-beta.6 - 2021-10-11 -- Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. [#374] -- Server no long listens to `SIGHUP` signal. Previously, the received was not used but did block - subsequent exit signals from working. [#389] -- Remove `config` module. `ServiceConfig`, `ServiceRuntime` public types are removed due to - this change. [#349] -- Remove `ServerBuilder::configure` [#349] +## 2.0.0-beta.5 -[#374]: https://github.com/actix/actix-net/pull/374 -[#349]: https://github.com/actix/actix-net/pull/349 -[#389]: https://github.com/actix/actix-net/pull/389 +- Server shutdown notifies all workers to exit regardless if shutdown is graceful. This causes all workers to shutdown immediately in force shutdown case. +## 2.0.0-beta.4 -## 2.0.0-beta.5 - 2021-04-20 -- Server shutdown notifies all workers to exit regardless if shutdown is graceful. This causes all - workers to shutdown immediately in force shutdown case. [#333] - -[#333]: https://github.com/actix/actix-net/pull/333 - - -## 2.0.0-beta.4 - 2021-04-01 - Prevent panic when `shutdown_timeout` is very large. [f9262db] -[f9262db]: https://github.com/actix/actix-net/commit/f9262db +## 2.0.0-beta.3 +- Hidden `ServerBuilder::start` method has been removed. Use `ServerBuilder::run`. +- Add retry for EINTR signal (`io::Interrupted`) in `Accept`'s poll loop. +- Add `ServerBuilder::worker_max_blocking_threads` to customize blocking thread pool size. +- Update `actix-rt` to `2.0.0`. -## 2.0.0-beta.3 - 2021-02-06 -- Hidden `ServerBuilder::start` method has been removed. Use `ServerBuilder::run`. [#246] -- Add retry for EINTR signal (`io::Interrupted`) in `Accept`'s poll loop. [#264] -- Add `ServerBuilder::worker_max_blocking_threads` to customize blocking thread pool size. [#265] -- Update `actix-rt` to `2.0.0`. [#273] +## 2.0.0-beta.2 -[#246]: https://github.com/actix/actix-net/pull/246 -[#264]: https://github.com/actix/actix-net/pull/264 -[#265]: https://github.com/actix/actix-net/pull/265 -[#273]: https://github.com/actix/actix-net/pull/273 +- Merge `actix-testing` to `actix-server` as `test_server` mod. +## 2.0.0-beta.1 -## 2.0.0-beta.2 - 2021-01-03 -- Merge `actix-testing` to `actix-server` as `test_server` mod. [#242] +- Added explicit info log message on accept queue pause. +- Prevent double registration of sockets when back-pressure is resolved. +- Update `mio` dependency to `0.7.3`. +- Remove `socket2` dependency. +- `ServerBuilder::backlog` now accepts `u32` instead of `i32`. +- Remove `AcceptNotify` type and pass `WakerQueue` to `Worker` to wake up `Accept`'s `Poll`. +- Convert `mio::net::TcpStream` to `actix_rt::net::TcpStream`(`UnixStream` for uds) using `FromRawFd` and `IntoRawFd`(`FromRawSocket` and `IntoRawSocket` on windows). +- Remove `AsyncRead` and `AsyncWrite` trait bound for `socket::FromStream` trait. -[#242]: https://github.com/actix/actix-net/pull/242 +## 1.0.4 - -## 2.0.0-beta.1 - 2020-12-28 -- Added explicit info log message on accept queue pause. [#215] -- Prevent double registration of sockets when back-pressure is resolved. [#223] -- Update `mio` dependency to `0.7.3`. [#239] -- Remove `socket2` dependency. [#239] -- `ServerBuilder::backlog` now accepts `u32` instead of `i32`. [#239] -- Remove `AcceptNotify` type and pass `WakerQueue` to `Worker` to wake up `Accept`'s `Poll`. [#239] -- Convert `mio::net::TcpStream` to `actix_rt::net::TcpStream`(`UnixStream` for uds) using - `FromRawFd` and `IntoRawFd`(`FromRawSocket` and `IntoRawSocket` on windows). [#239] -- Remove `AsyncRead` and `AsyncWrite` trait bound for `socket::FromStream` trait. [#239] - -[#215]: https://github.com/actix/actix-net/pull/215 -[#223]: https://github.com/actix/actix-net/pull/223 -[#239]: https://github.com/actix/actix-net/pull/239 - - -## 1.0.4 - 2020-09-12 - Update actix-codec to 0.3.0. -- Workers must be greater than 0. [#167] +- Workers must be greater than 0. -[#167]: https://github.com/actix/actix-net/pull/167 +## 1.0.3 +- Replace deprecated `net2` crate with `socket2`. -## 1.0.3 - 2020-05-19 -- Replace deprecated `net2` crate with `socket2` [#140] +## 1.0.2 -[#140]: https://github.com/actix/actix-net/pull/140 +- Avoid error by calling `reregister()` on Windows. +## 1.0.1 -## 1.0.2 - 2020-02-26 -- Avoid error by calling `reregister()` on Windows [#103] - -[#103]: https://github.com/actix/actix-net/pull/103 - - -## 1.0.1 - 2019-12-29 - Rename `.start()` method to `.run()` +## 1.0.0 -## 1.0.0 - 2019-12-11 - Use actix-net releases +## 1.0.0-alpha.4 -## 1.0.0-alpha.4 - 2019-12-08 - Use actix-service 1.0.0-alpha.4 +## 1.0.0-alpha.3 -## 1.0.0-alpha.3 - 2019-12-07 - Migrate to tokio 0.2 - Fix compilation on non-unix platforms - Better handling server configuration +## 1.0.0-alpha.2 -## 1.0.0-alpha.2 - 2019-12-02 - Simplify server service (remove actix-server-config) - Allow to wait on `Server` until server stops +## 0.8.0-alpha.1 -## 0.8.0-alpha.1 - 2019-11-22 - Migrate to `std::future` +## 0.7.0 -## 0.7.0 - 2019-10-04 - Update `rustls` to 0.16 - Minimum required Rust version upped to 1.37.0 +## 0.6.1 -## 0.6.1 - 2019-09-25 - Add UDS listening support to `ServerBuilder` +## 0.6.0 -## 0.6.0 - 2019-07-18 - Support Unix domain sockets #3 +## 0.5.1 -## 0.5.1 - 2019-05-18 - ServerBuilder::shutdown_timeout() accepts u64 +## 0.5.0 -## 0.5.0 - 2019-05-12 - Add `Debug` impl for `SslError` - Derive debug for `Server` and `ServerCommand` - Upgrade to actix-service 0.4 +## 0.4.3 -## 0.4.3 - 2019-04-16 - Re-export `IoStream` trait - Depend on `ssl` and `rust-tls` features from actix-server-config +## 0.4.2 -## 0.4.2 - 2019-03-30 - Fix SIGINT force shutdown +## 0.4.1 -## 0.4.1 - 2019-03-14 - `SystemRuntime::on_start()` - allow to run future before server service initialization +## 0.4.0 -## 0.4.0 - 2019-03-12 - Use `ServerConfig` for service factory - Wrap tcp socket to `Io` type - Upgrade actix-service +## 0.3.1 -## 0.3.1 - 2019-03-04 - Add `ServerBuilder::maxconnrate` sets the maximum per-worker number of concurrent connections - Add helper ssl error `SslError` - Rename `StreamServiceFactory` to `ServiceFactory` - Deprecate `StreamServiceFactory` +## 0.3.0 -## 0.3.0 - 2019-03-02 - Use new `NewService` trait +## 0.2.1 -## 0.2.1 - 2019-02-09 - Drop service response +## 0.2.0 -## 0.2.0 - 2019-02-01 - Migrate to actix-service 0.2 - Updated rustls dependency +## 0.1.3 -## 0.1.3 - 2018-12-21 - Fix max concurrent connections handling +## 0.1.2 -## 0.1.2 - 2018-12-12 - rename ServiceConfig::rt() to ServiceConfig::apply() - Fix back-pressure for concurrent ssl handshakes +## 0.1.1 -## 0.1.1 - 2018-12-11 - Fix signal handling on windows +## 0.1.0 -## 0.1.0 - 2018-12-09 - Move server to separate crate diff --git a/actix-server/Cargo.toml b/actix-server/Cargo.toml old mode 100755 new mode 100644 index 23701332..3fb037dc --- a/actix-server/Cargo.toml +++ b/actix-server/Cargo.toml @@ -1,50 +1,53 @@ [package] name = "actix-server" -version = "2.0.0" +version = "2.6.0" authors = [ - "Nikolay Kim ", - "fakeshadow <24548779@qq.com>", - "Rob Ede ", - "Ali MJ Al-Nasrawy ", + "Nikolay Kim ", + "Rob Ede ", + "Ali MJ Al-Nasrawy ", ] description = "General purpose TCP server built for the Actix ecosystem" keywords = ["network", "tcp", "server", "framework", "async"] categories = ["network-programming", "asynchronous"] homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-net.git" +repository = "https://github.com/actix/actix-net/tree/main/actix-server" license = "MIT OR Apache-2.0" -edition = "2018" +edition.workspace = true +rust-version.workspace = true -[lib] -name = "actix_server" -path = "src/lib.rs" +[package.metadata.cargo_check_external_types] +allowed_external_types = ["tokio::*"] [features] default = [] io-uring = ["tokio-uring", "actix-rt/io-uring"] [dependencies] -actix-rt = { version = "2.6.0", default-features = false } -actix-service = "2.0.0" -actix-utils = "3.0.0" +actix-rt = { version = "2.10", default-features = false } +actix-service = "2" +actix-utils = "3" +futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] } +mio = { version = "1", features = ["os-poll", "net"] } +socket2 = "0.6" +tokio = { version = "1.44.2", features = ["sync"] } +tracing = { version = "0.1.30", default-features = false, features = ["log"] } -futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } -futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } -log = "0.4" -mio = { version = "0.8", features = ["os-poll", "net"] } -num_cpus = "1.13" -socket2 = "0.4.2" -tokio = { version = "1.13.1", features = ["sync"] } - -# runtime for io-uring feature +# runtime for `io-uring` feature [target.'cfg(target_os = "linux")'.dependencies] -tokio-uring = { version = "0.2", optional = true } +tokio-uring = { version = "0.5", optional = true } [dev-dependencies] -actix-codec = "0.4.2" -actix-rt = "2.6.0" +actix-codec = "0.5" +actix-rt = "2.8" bytes = "1" -env_logger = "0.9" -futures-util = { version = "0.3.7", default-features = false, features = ["sink", "async-await-macro"] } -tokio = { version = "1.13.1", features = ["io-util", "rt-multi-thread", "macros", "fs"] } +futures-util = { version = "0.3.17", default-features = false, features = ["sink", "async-await-macro"] } +pretty_env_logger = "0.5" +static_assertions = "1" +tokio = { version = "1.44.2", features = ["io-util", "rt-multi-thread", "macros", "fs", "time"] } +tokio-util = "0.7" +tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] } + +[lints] +workspace = true diff --git a/actix-server/README.md b/actix-server/README.md index e4ef95b1..f7bcda42 100644 --- a/actix-server/README.md +++ b/actix-server/README.md @@ -2,14 +2,20 @@ > General purpose TCP server built for the Actix ecosystem. + + [![crates.io](https://img.shields.io/crates/v/actix-server?label=latest)](https://crates.io/crates/actix-server) -[![Documentation](https://docs.rs/actix-server/badge.svg?version=2.0.0)](https://docs.rs/actix-server/2.0.0) -[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) -![License](https://img.shields.io/crates/l/actix-server.svg) -[![Dependency Status](https://deps.rs/crate/actix-server/2.0.0/status.svg)](https://deps.rs/crate/actix-server/2.0.0) +[![Documentation](https://docs.rs/actix-server/badge.svg?version=2.6.0)](https://docs.rs/actix-server/2.6.0) +[![Version](https://img.shields.io/badge/rustc-1.88+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-server.svg) +
+[![Dependency Status](https://deps.rs/crate/actix-server/2.6.0/status.svg)](https://deps.rs/crate/actix-server/2.6.0) ![Download](https://img.shields.io/crates/d/actix-server.svg) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) + + ## Resources + - [Library Documentation](https://docs.rs/actix-server) - [Examples](/actix-server/examples) diff --git a/actix-server/examples/file-reader.rs b/actix-server/examples/file-reader.rs index 3cc991d3..06de4653 100644 --- a/actix-server/examples/file-reader.rs +++ b/actix-server/examples/file-reader.rs @@ -8,6 +8,8 @@ //! //! Follow the prompt and enter a file path, relative or absolute. +#![allow(missing_docs)] + use std::io; use actix_codec::{Framed, LinesCodec}; @@ -18,10 +20,11 @@ use futures_util::{SinkExt as _, StreamExt as _}; use tokio::{fs::File, io::AsyncReadExt as _}; async fn run() -> io::Result<()> { - env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); + pretty_env_logger::formatted_timed_builder() + .parse_env(pretty_env_logger::env_logger::Env::default().default_filter_or("info")); let addr = ("127.0.0.1", 8080); - log::info!("starting server on port: {}", &addr.0); + tracing::info!("starting server on port: {}", &addr.0); // Bind socket address and start worker(s). By default, the server uses the number of physical // CPU cores as the worker count. For this reason, the closure passed to bind needs to return @@ -39,8 +42,10 @@ async fn run() -> io::Result<()> { // wait for next line match framed.next().await { Some(Ok(line)) => { - match File::open(line).await { + match File::open(&line).await { Ok(mut file) => { + tracing::info!("reading file: {}", &line); + // read file into String buffer let mut buf = String::new(); file.read_to_string(&mut buf).await?; @@ -52,7 +57,7 @@ async fn run() -> io::Result<()> { break; } Err(err) => { - log::error!("{}", err); + tracing::error!("{}", err); framed .send("File not found or not readable. Try again.") .await?; @@ -72,7 +77,7 @@ async fn run() -> io::Result<()> { // close connection after file has been copied to TCP stream Ok(()) }) - .map_err(|err| log::error!("Service Error: {:?}", err)) + .map_err(|err| tracing::error!("service error: {:?}", err)) })? .workers(2) .run() diff --git a/actix-server/examples/shutdown-signal.rs b/actix-server/examples/shutdown-signal.rs new file mode 100644 index 00000000..99500845 --- /dev/null +++ b/actix-server/examples/shutdown-signal.rs @@ -0,0 +1,51 @@ +//! Demonstrates use of the `ServerBuilder::shutdown_signal` method using `tokio-util`s +//! `CancellationToken` helper using a nonsensical timer. In practice, this cancellation token would +//! be wired throughout your application and typically triggered by OS signals elsewhere. + +use std::{io, time::Duration}; + +use actix_rt::net::TcpStream; +use actix_server::Server; +use actix_service::fn_service; +use tokio_util::sync::CancellationToken; +use tracing::level_filters::LevelFilter; +use tracing_subscriber::{prelude::*, EnvFilter}; + +async fn run(stop_signal: CancellationToken) -> io::Result<()> { + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with( + EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(), + ) + .init(); + + let addr = ("127.0.0.1", 8080); + tracing::info!("starting server on port: {}", &addr.0); + + Server::build() + .bind("shutdown-signal", addr, || { + fn_service(|_stream: TcpStream| async { Ok::<_, io::Error>(()) }) + })? + .shutdown_signal(stop_signal.cancelled_owned()) + .workers(2) + .run() + .await +} + +#[tokio::main] +async fn main() -> io::Result<()> { + let stop_signal = CancellationToken::new(); + + tokio::spawn({ + let stop_signal = stop_signal.clone(); + async move { + tokio::time::sleep(Duration::from_secs(10)).await; + stop_signal.cancel(); + } + }); + + run(stop_signal).await?; + Ok(()) +} diff --git a/actix-server/examples/tcp-echo.rs b/actix-server/examples/tcp-echo.rs index c84910c9..7f03bf65 100644 --- a/actix-server/examples/tcp-echo.rs +++ b/actix-server/examples/tcp-echo.rs @@ -22,16 +22,16 @@ use actix_server::Server; use actix_service::{fn_service, ServiceFactoryExt as _}; use bytes::BytesMut; use futures_util::future::ok; -use log::{error, info}; use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; async fn run() -> io::Result<()> { - env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); + pretty_env_logger::formatted_timed_builder() + .parse_env(pretty_env_logger::env_logger::Env::default().default_filter_or("info")); let count = Arc::new(AtomicUsize::new(0)); let addr = ("127.0.0.1", 8080); - info!("starting server on: {}:{}", &addr.0, &addr.1); + tracing::info!("starting server on: {}:{}", &addr.0, &addr.1); // Bind socket address and start worker(s). By default, the server uses the number of physical // CPU cores as the worker count. For this reason, the closure passed to bind needs to return @@ -58,14 +58,14 @@ async fn run() -> io::Result<()> { // more bytes to process Ok(bytes_read) => { - info!("[{}] read {} bytes", num, bytes_read); + tracing::info!("[{}] read {} bytes", num, bytes_read); stream.write_all(&buf[size..]).await.unwrap(); size += bytes_read; } // stream error; bail from loop with error Err(err) => { - error!("Stream Error: {:?}", err); + tracing::error!("stream error: {:?}", err); return Err(()); } } @@ -75,10 +75,10 @@ async fn run() -> io::Result<()> { Ok((buf.freeze(), size)) } }) - .map_err(|err| error!("Service Error: {:?}", err)) + .map_err(|err| tracing::error!("service error: {:?}", err)) .and_then(move |(_, size)| { let num = num2.load(Ordering::SeqCst); - info!("[{}] total bytes read: {}", num, size); + tracing::info!("[{}] total bytes read: {}", num, size); ok(size) }) })? diff --git a/actix-server/src/accept.rs b/actix-server/src/accept.rs index 9f7872f8..921a7b1e 100644 --- a/actix-server/src/accept.rs +++ b/actix-server/src/accept.rs @@ -1,8 +1,8 @@ use std::{io, thread, time::Duration}; use actix_rt::time::Instant; -use log::{debug, error, info}; use mio::{Interest, Poll, Token as MioToken}; +use tracing::{debug, error, info}; use crate::{ availability::Availability, @@ -24,7 +24,7 @@ struct ServerSocketInfo { timeout: Option, } -/// poll instance of the server. +/// Poll instance of the server. pub(crate) struct Accept { poll: Poll, waker_queue: WakerQueue, @@ -41,7 +41,7 @@ impl Accept { pub(crate) fn start( sockets: Vec<(usize, MioListener)>, builder: &ServerBuilder, - ) -> io::Result<(WakerQueue, Vec)> { + ) -> io::Result<(WakerQueue, Vec, thread::JoinHandle<()>)> { let handle_server = ServerHandle::new(builder.cmd_tx.clone()); // construct poll instance and its waker @@ -73,12 +73,12 @@ impl Accept { handle_server, )?; - thread::Builder::new() + let accept_handle = thread::Builder::new() .name("actix-server acceptor".to_owned()) .spawn(move || accept.poll_with(&mut sockets)) - .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; + .map_err(io::Error::other)?; - Ok((waker_queue, handles_server)) + Ok((waker_queue, handles_server, accept_handle)) } fn new_with_sockets( @@ -130,7 +130,7 @@ impl Accept { if let Err(err) = self.poll.poll(&mut events, self.timeout) { match err.kind() { io::ErrorKind::Interrupted => {} - _ => panic!("Poll error: {}", err), + _ => panic!("Poll error: {err}"), } } @@ -140,7 +140,7 @@ impl Accept { WAKER_TOKEN => { let exit = self.handle_waker(sockets); if exit { - info!("Accept thread stopped"); + info!("accept thread stopped"); return; } } @@ -161,11 +161,12 @@ impl Accept { // a loop that would try to drain the command channel. It's yet unknown // if it's necessary/good practice to actively drain the waker queue. loop { - // take guard with every iteration so no new interest can be added - // until the current task is done. + // Take guard with every iteration so no new interests can be added until the current + // task is done. Take care not to take the guard again inside this loop. let mut guard = self.waker_queue.guard(); + match guard.pop_front() { - // worker notify it becomes available. + // Worker notified it became available. Some(WakerInterest::WorkerAvailable(idx)) => { drop(guard); @@ -176,7 +177,7 @@ impl Accept { } } - // a new worker thread is made and it's handle would be added to Accept + // A new worker thread has been created so store its handle. Some(WakerInterest::Worker(handle)) => { drop(guard); @@ -297,16 +298,16 @@ impl Accept { fn register_logged(&self, info: &mut ServerSocketInfo) { match self.register(info) { - Ok(_) => debug!("Resume accepting connections on {}", info.lst.local_addr()), - Err(err) => error!("Can not register server socket {}", err), + Ok(_) => debug!("resume accepting connections on {}", info.lst.local_addr()), + Err(err) => error!("can not register server socket {}", err), } } fn deregister_logged(&self, info: &mut ServerSocketInfo) { match self.poll.registry().deregister(&mut info.lst) { - Ok(_) => debug!("Paused accepting connections on {}", info.lst.local_addr()), + Ok(_) => debug!("paused accepting connections on {}", info.lst.local_addr()), Err(err) => { - error!("Can not deregister server socket {}", err) + error!("can not deregister server socket {}", err) } } } @@ -350,7 +351,7 @@ impl Accept { self.remove_next(); if self.handles.is_empty() { - error!("No workers"); + error!("no workers"); // All workers are gone and Conn is nowhere to be sent. // Treat this situation as Ok and drop Conn. return Ok(()); @@ -399,7 +400,7 @@ impl Accept { Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => return, Err(ref err) if connection_error(err) => continue, Err(err) => { - error!("Error accepting connection: {}", err); + error!("error accepting connection: {}", err); // deregister listener temporary self.deregister_logged(info); @@ -453,8 +454,8 @@ impl Accept { /// All other errors will incur a timeout before next `accept()` call is attempted. The timeout is /// useful to handle resource exhaustion errors like `ENFILE` and `EMFILE`. Otherwise, it could /// enter into a temporary spin loop. -fn connection_error(e: &io::Error) -> bool { - e.kind() == io::ErrorKind::ConnectionRefused - || e.kind() == io::ErrorKind::ConnectionAborted - || e.kind() == io::ErrorKind::ConnectionReset +fn connection_error(err: &io::Error) -> bool { + err.kind() == io::ErrorKind::ConnectionRefused + || err.kind() == io::ErrorKind::ConnectionAborted + || err.kind() == io::ErrorKind::ConnectionReset } diff --git a/actix-server/src/builder.rs b/actix-server/src/builder.rs index c3bc0269..864132ef 100644 --- a/actix-server/src/builder.rs +++ b/actix-server/src/builder.rs @@ -1,19 +1,35 @@ -use std::{io, time::Duration}; +use std::{future::Future, io, num::NonZeroUsize, time::Duration}; use actix_rt::net::TcpStream; -use log::{info, trace}; +use futures_core::future::BoxFuture; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use crate::{ server::ServerCommand, service::{InternalServiceFactory, ServerServiceFactory, StreamNewService}, - socket::{ - create_mio_tcp_listener, MioListener, MioTcpListener, StdTcpListener, ToSocketAddrs, - }, + socket::{create_mio_tcp_listener, MioListener, MioTcpListener, StdTcpListener, ToSocketAddrs}, worker::ServerWorkerConfig, Server, }; +/// Multipath TCP (MPTCP) preference. +/// +/// Currently only useful on Linux. +/// +#[cfg_attr(target_os = "linux", doc = "Also see [`ServerBuilder::mptcp()`].")] +#[derive(Debug, Clone)] +pub enum MpTcp { + /// MPTCP will not be used when binding sockets. + Disabled, + + /// MPTCP will be attempted when binding sockets. If errors occur, regular TCP will be + /// attempted, too. + TcpFallback, + + /// MPTCP will be used when binding sockets (with no fallback). + NoFallback, +} + /// [Server] builder. pub struct ServerBuilder { pub(crate) threads: usize, @@ -21,8 +37,10 @@ pub struct ServerBuilder { pub(crate) backlog: u32, pub(crate) factories: Vec>, pub(crate) sockets: Vec<(usize, String, MioListener)>, + pub(crate) mptcp: MpTcp, pub(crate) exit: bool, pub(crate) listen_os_signals: bool, + pub(crate) shutdown_signal: Option>, pub(crate) cmd_tx: UnboundedSender, pub(crate) cmd_rx: UnboundedReceiver, pub(crate) worker_config: ServerWorkerConfig, @@ -40,26 +58,34 @@ impl ServerBuilder { let (cmd_tx, cmd_rx) = unbounded_channel(); ServerBuilder { - threads: num_cpus::get_physical(), + threads: std::thread::available_parallelism().map_or(2, NonZeroUsize::get), token: 0, factories: Vec::new(), sockets: Vec::new(), backlog: 2048, + mptcp: MpTcp::Disabled, exit: false, listen_os_signals: true, + shutdown_signal: None, cmd_tx, cmd_rx, worker_config: ServerWorkerConfig::default(), } } - /// Set number of workers to start. + /// Sets number of workers to start. + /// + /// See [`bind()`](Self::bind()) for more details on how worker count affects the number of + /// server factory instantiations. + /// + /// The default worker count is the determined by [`std::thread::available_parallelism()`]. See + /// its documentation to determine what behavior you should expect when server is run. /// /// `num` must be greater than 0. /// - /// The default worker count is the number of physical CPU cores available. If your benchmark - /// testing indicates that simultaneous multi-threading is beneficial to your app, you can use - /// the [`num_cpus`] crate to acquire the _logical_ core count instead. + /// # Panics + /// + /// Panics if `num` is 0. pub fn workers(mut self, num: usize) -> Self { assert_ne!(num, 0, "workers must be greater than 0"); self.threads = num; @@ -98,6 +124,24 @@ impl ServerBuilder { self } + /// Sets MultiPath TCP (MPTCP) preference on bound sockets. + /// + /// Multipath TCP (MPTCP) builds on top of TCP to improve connection redundancy and performance + /// by sharing a network data stream across multiple underlying TCP sessions. See [mptcp.dev] + /// for more info about MPTCP itself. + /// + /// MPTCP is available on Linux kernel version 5.6 and higher. In addition, you'll also need to + /// ensure the kernel option is enabled using `sysctl net.mptcp.enabled=1`. + /// + /// This method will have no effect if called after a `bind()`. + /// + /// [mptcp.dev]: https://www.mptcp.dev + #[cfg(target_os = "linux")] + pub fn mptcp(mut self, mptcp_enabled: MpTcp) -> Self { + self.mptcp = mptcp_enabled; + self + } + /// Sets the maximum per-worker number of concurrent connections. /// /// All socket listeners will stop accepting connections when this limit is reached for @@ -115,18 +159,55 @@ impl ServerBuilder { self.max_concurrent_connections(num) } - /// Stop Actix `System` after server shutdown. + /// Sets flag to stop Actix `System` after server shutdown. + /// + /// This has no effect when server is running in a Tokio-only runtime. pub fn system_exit(mut self) -> Self { self.exit = true; self } - /// Disable OS signal handling. + /// Disables OS signal handling. pub fn disable_signals(mut self) -> Self { self.listen_os_signals = false; self } + /// Specify shutdown signal from a future. + /// + /// Using this method will prevent OS signal handlers being set up. + /// + /// Typically, a `CancellationToken` will be used, but any future _can_ be. + /// + /// # Examples + /// + /// ``` + /// # use std::io; + /// # use tokio::net::TcpStream; + /// # use actix_server::Server; + /// # async fn run() -> io::Result<()> { + /// use actix_service::fn_service; + /// use tokio_util::sync::CancellationToken; + /// + /// let stop_signal = CancellationToken::new(); + /// + /// Server::build() + /// .bind("shutdown-signal", "127.0.0.1:12345", || { + /// fn_service(|_stream: TcpStream| async { Ok::<_, io::Error>(()) }) + /// })? + /// .shutdown_signal(stop_signal.cancelled_owned()) + /// .run() + /// .await + /// # } + /// ``` + pub fn shutdown_signal(mut self, shutdown_signal: Fut) -> Self + where + Fut: Future + Send + 'static, + { + self.shutdown_signal = Some(Box::pin(shutdown_signal)); + self + } + /// Timeout for graceful workers shutdown in seconds. /// /// After receiving a stop signal, workers have this much time to finish serving requests. @@ -139,25 +220,49 @@ impl ServerBuilder { self } - /// Add new service to the server. - pub fn bind(mut self, name: N, addr: U, factory: F) -> io::Result + /// Adds new service to the server. + /// + /// Note that, if a DNS lookup is required, resolving hostnames is a blocking operation. + /// + /// # Worker Count + /// + /// The `factory` will be instantiated multiple times in most scenarios. The number of + /// instantiations is number of [`workers`](Self::workers()) ร— number of sockets resolved by + /// `addrs`. + /// + /// For example, if you've manually set [`workers`](Self::workers()) to 2, and use `127.0.0.1` + /// as the bind `addrs`, then `factory` will be instantiated twice. However, using `localhost` + /// as the bind `addrs` can often resolve to both `127.0.0.1` (IPv4) _and_ `::1` (IPv6), causing + /// the `factory` to be instantiated 4 times (2 workers ร— 2 bind addresses). + /// + /// Using a bind address of `0.0.0.0`, which signals to use all interfaces, may also multiple + /// the number of instantiations in a similar way. + /// + /// # Errors + /// + /// Returns an `io::Error` if: + /// - `addrs` cannot be resolved into one or more socket addresses; + /// - all the resolved socket addresses are already bound. + pub fn bind(mut self, name: N, addrs: U, factory: F) -> io::Result where F: ServerServiceFactory, U: ToSocketAddrs, N: AsRef, { - let sockets = bind_addr(addr, self.backlog)?; + let sockets = bind_addr(addrs, self.backlog, &self.mptcp)?; - trace!("binding server to: {:?}", &sockets); + tracing::trace!("binding server to: {sockets:?}"); for lst in sockets { let token = self.next_token(); + self.factories.push(StreamNewService::create( name.as_ref().to_string(), token, factory.clone(), lst.local_addr()?, )); + self.sockets .push((token, name.as_ref().to_string(), MioListener::Tcp(lst))); } @@ -165,7 +270,12 @@ impl ServerBuilder { Ok(self) } - /// Add new service to the server. + /// Adds service to the server using a socket listener already bound. + /// + /// # Worker Count + /// + /// The `factory` will be instantiated multiple times in most scenarios. The number of + /// instantiations is: number of [`workers`](Self::workers()). pub fn listen>( mut self, name: N, @@ -197,7 +307,7 @@ impl ServerBuilder { if self.sockets.is_empty() { panic!("Server should have at least one bound socket"); } else { - info!("Starting {} workers", self.threads); + tracing::info!("starting {} workers", self.threads); Server::new(self) } } @@ -211,7 +321,12 @@ impl ServerBuilder { #[cfg(unix)] impl ServerBuilder { - /// Add new unix domain service to the server. + /// Adds new service to the server using a UDS (unix domain socket) address. + /// + /// # Worker Count + /// + /// The `factory` will be instantiated multiple times in most scenarios. The number of + /// instantiations is: number of [`workers`](Self::workers()). pub fn bind_uds(self, name: N, addr: U, factory: F) -> io::Result where F: ServerServiceFactory, @@ -231,9 +346,14 @@ impl ServerBuilder { self.listen_uds(name, lst, factory) } - /// Add new unix domain service to the server. + /// Adds new service to the server using a UDS (unix domain socket) listener already bound. /// /// Useful when running as a systemd service and a socket FD is acquired externally. + /// + /// # Worker Count + /// + /// The `factory` will be instantiated multiple times in most scenarios. The number of + /// instantiations is: number of [`workers`](Self::workers()). pub fn listen_uds>( mut self, name: N, @@ -244,18 +364,22 @@ impl ServerBuilder { F: ServerServiceFactory, { use std::net::{IpAddr, Ipv4Addr}; + lst.set_nonblocking(true)?; + let token = self.next_token(); - let addr = - crate::socket::StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + let addr = crate::socket::StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); + self.factories.push(StreamNewService::create( name.as_ref().to_string(), token, factory, addr, )); + self.sockets .push((token, name.as_ref().to_string(), MioListener::from(lst))); + Ok(self) } } @@ -263,13 +387,14 @@ impl ServerBuilder { pub(super) fn bind_addr( addr: S, backlog: u32, + mptcp: &MpTcp, ) -> io::Result> { let mut opt_err = None; let mut success = false; let mut sockets = Vec::new(); for addr in addr.to_socket_addrs()? { - match create_mio_tcp_listener(addr, backlog) { + match create_mio_tcp_listener(addr, backlog, mptcp) { Ok(lst) => { success = true; sockets.push(lst); @@ -283,9 +408,6 @@ pub(super) fn bind_addr( } else if let Some(err) = opt_err.take() { Err(err) } else { - Err(io::Error::new( - io::ErrorKind::Other, - "Can not bind to address.", - )) + Err(io::Error::other("Can not bind to address.")) } } diff --git a/actix-server/src/join_all.rs b/actix-server/src/join_all.rs index bdef62ef..b7a33c8b 100644 --- a/actix-server/src/join_all.rs +++ b/actix-server/src/join_all.rs @@ -63,9 +63,9 @@ impl Future for JoinAll { #[cfg(test)] mod test { - use super::*; + use core::future::ready; - use actix_utils::future::ready; + use super::*; #[actix_rt::test] async fn test_join_all() { diff --git a/actix-server/src/lib.rs b/actix-server/src/lib.rs index 9bbb33a0..8660d548 100644 --- a/actix-server/src/lib.rs +++ b/actix-server/src/lib.rs @@ -1,7 +1,5 @@ //! General purpose TCP server. -#![deny(rust_2018_idioms, nonstandard_style)] -#![warn(future_incompatible)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] @@ -18,14 +16,15 @@ mod test_server; mod waker_queue; mod worker; -pub use self::builder::ServerBuilder; -pub use self::handle::ServerHandle; -pub use self::server::Server; -pub use self::service::ServerServiceFactory; -pub use self::test_server::TestServer; - #[doc(hidden)] pub use self::socket::FromStream; +pub use self::{ + builder::{MpTcp, ServerBuilder}, + handle::ServerHandle, + server::Server, + service::ServerServiceFactory, + test_server::TestServer, +}; /// Start server building process #[doc(hidden)] diff --git a/actix-server/src/server.rs b/actix-server/src/server.rs index fec3b06e..0d582978 100644 --- a/actix-server/src/server.rs +++ b/actix-server/src/server.rs @@ -3,21 +3,22 @@ use std::{ io, mem, pin::Pin, task::{Context, Poll}, + thread, time::Duration, }; use actix_rt::{time::sleep, System}; use futures_core::{future::BoxFuture, Stream}; use futures_util::stream::StreamExt as _; -use log::{error, info}; use tokio::sync::{mpsc::UnboundedReceiver, oneshot}; +use tracing::{error, info}; use crate::{ accept::Accept, builder::ServerBuilder, join_all::join_all, service::InternalServiceFactory, - signals::{SignalKind, Signals}, + signals::{OsSignals, SignalKind, StopSignal}, waker_queue::{WakerInterest, WakerQueue}, worker::{ServerWorker, ServerWorkerConfig, WorkerHandleServer}, ServerHandle, @@ -65,7 +66,7 @@ pub(crate) enum ServerCommand { /// server has fully shut down. /// /// # Shutdown Signals -/// On UNIX systems, `SIGQUIT` will start a graceful shutdown and `SIGTERM` or `SIGINT` will start a +/// On UNIX systems, `SIGTERM` will start a graceful shutdown and `SIGQUIT` or `SIGINT` will start a /// forced shutdown. On Windows, a Ctrl-C signal will start a forced shutdown. /// /// A graceful shutdown will wait for all workers to stop first. @@ -158,6 +159,7 @@ impl Future for Server { pub struct ServerInner { worker_handles: Vec, + accept_handle: Option>, worker_config: ServerWorkerConfig, services: Vec>, waker_queue: WakerQueue, @@ -181,11 +183,6 @@ impl ServerInner { } fn run_sync(mut builder: ServerBuilder) -> io::Result<(Self, ServerEventMultiplexer)> { - let sockets = mem::take(&mut builder.sockets) - .into_iter() - .map(|t| (t.0, t.2)) - .collect(); - // Give log information on what runtime will be used. let is_actix = actix_rt::System::try_current().is_some(); let is_tokio = tokio::runtime::Handle::try_current().is_ok(); @@ -198,22 +195,33 @@ impl ServerInner { for (_, name, lst) in &builder.sockets { info!( - r#"Starting service: "{}", workers: {}, listening on: {}"#, + r#"starting service: "{}", workers: {}, listening on: {}"#, name, builder.threads, lst.local_addr() ); } - let (waker_queue, worker_handles) = Accept::start(sockets, &builder)?; + let sockets = mem::take(&mut builder.sockets) + .into_iter() + .map(|t| (t.0, t.2)) + .collect(); + + let (waker_queue, worker_handles, accept_handle) = Accept::start(sockets, &builder)?; let mux = ServerEventMultiplexer { - signal_fut: (builder.listen_os_signals).then(Signals::new), + signal_fut: builder.shutdown_signal.map(StopSignal::Cancel).or_else(|| { + builder + .listen_os_signals + .then(OsSignals::new) + .map(StopSignal::Os) + }), cmd_rx: builder.cmd_rx, }; let server = ServerInner { waker_queue, + accept_handle: Some(accept_handle), worker_handles, worker_config: builder.worker_config, services: builder.factories, @@ -243,7 +251,8 @@ impl ServerInner { } => { self.stopping = true; - // stop accept thread + // Signal accept thread to stop. + // Signal is non-blocking; we wait for thread to stop later. self.waker_queue.wake(WakerInterest::Stop); // send stop signal to workers @@ -258,6 +267,13 @@ impl ServerInner { let _ = join_all(workers_stop).await; } + // wait for accept thread stop + self.accept_handle + .take() + .unwrap() + .join() + .expect("Accept thread must not panic in any case"); + if let Some(tx) = completion { let _ = tx.send(()); } @@ -272,7 +288,7 @@ impl ServerInner { // TODO: maybe just return with warning log if not found ? assert!(self.worker_handles.iter().any(|wrk| wrk.idx == idx)); - error!("Worker {} has died; restarting", idx); + error!("worker {} has died; restarting", idx); let factories = self .services @@ -304,7 +320,16 @@ impl ServerInner { fn map_signal(signal: SignalKind) -> ServerCommand { match signal { - SignalKind::Int => { + SignalKind::Cancel => { + info!("Cancellation token/channel received; starting graceful shutdown"); + ServerCommand::Stop { + graceful: true, + completion: None, + force_system_stop: true, + } + } + + SignalKind::OsInt => { info!("SIGINT received; starting forced shutdown"); ServerCommand::Stop { graceful: false, @@ -313,7 +338,7 @@ impl ServerInner { } } - SignalKind::Term => { + SignalKind::OsTerm => { info!("SIGTERM received; starting graceful shutdown"); ServerCommand::Stop { graceful: true, @@ -322,7 +347,7 @@ impl ServerInner { } } - SignalKind::Quit => { + SignalKind::OsQuit => { info!("SIGQUIT received; starting forced shutdown"); ServerCommand::Stop { graceful: false, @@ -336,7 +361,7 @@ impl ServerInner { struct ServerEventMultiplexer { cmd_rx: UnboundedReceiver, - signal_fut: Option, + signal_fut: Option, } impl Stream for ServerEventMultiplexer { @@ -352,6 +377,6 @@ impl Stream for ServerEventMultiplexer { } } - Pin::new(&mut this.cmd_rx).poll_recv(cx) + this.cmd_rx.poll_recv(cx) } } diff --git a/actix-server/src/service.rs b/actix-server/src/service.rs index 3a9aeee4..2de89702 100644 --- a/actix-server/src/service.rs +++ b/actix-server/src/service.rs @@ -1,3 +1,4 @@ +use core::future::{ready, Ready}; use std::{ marker::PhantomData, net::SocketAddr, @@ -5,9 +6,8 @@ use std::{ }; use actix_service::{Service, ServiceFactory as BaseServiceFactory}; -use actix_utils::future::{ready, Ready}; use futures_core::future::LocalBoxFuture; -use log::error; +use tracing::error; use crate::{ socket::{FromStream, MioStream}, @@ -78,7 +78,7 @@ where Ok(()) } Err(err) => { - error!("Can not convert to an async tcp stream: {}", err); + error!("can not convert to an async TCP stream: {err}"); Err(()) } }) diff --git a/actix-server/src/signals.rs b/actix-server/src/signals.rs index d8cb84e3..07090b6c 100644 --- a/actix-server/src/signals.rs +++ b/actix-server/src/signals.rs @@ -1,39 +1,63 @@ use std::{ fmt, future::Future, - pin::Pin, + pin::{pin, Pin}, task::{Context, Poll}, }; -use log::trace; +use futures_core::future::BoxFuture; +use tracing::trace; /// Types of process signals. // #[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq)] #[allow(dead_code)] // variants are never constructed on non-unix pub(crate) enum SignalKind { - /// `SIGINT` - Int, + /// Cancellation token or channel. + Cancel, - /// `SIGTERM` - Term, + /// OS `SIGINT`. + OsInt, - /// `SIGQUIT` - Quit, + /// OS `SIGTERM`. + OsTerm, + + /// OS `SIGQUIT`. + OsQuit, } impl fmt::Display for SignalKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { - SignalKind::Int => "SIGINT", - SignalKind::Term => "SIGTERM", - SignalKind::Quit => "SIGQUIT", + SignalKind::Cancel => "Cancellation token or channel", + SignalKind::OsInt => "SIGINT", + SignalKind::OsTerm => "SIGTERM", + SignalKind::OsQuit => "SIGQUIT", }) } } +pub(crate) enum StopSignal { + /// OS signal handling is configured. + Os(OsSignals), + + /// Cancellation token or channel. + Cancel(BoxFuture<'static, ()>), +} + +impl Future for StopSignal { + type Output = SignalKind; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.get_mut() { + StopSignal::Os(os_signals) => pin!(os_signals).poll(cx), + StopSignal::Cancel(cancel) => pin!(cancel).poll(cx).map(|()| SignalKind::Cancel), + } + } +} + /// Process signal listener. -pub(crate) struct Signals { +pub(crate) struct OsSignals { #[cfg(not(unix))] signals: futures_core::future::BoxFuture<'static, std::io::Result<()>>, @@ -41,14 +65,14 @@ pub(crate) struct Signals { signals: Vec<(SignalKind, actix_rt::signal::unix::Signal)>, } -impl Signals { +impl OsSignals { /// Constructs an OS signal listening future. pub(crate) fn new() -> Self { trace!("setting up OS signal listener"); #[cfg(not(unix))] { - Signals { + OsSignals { signals: Box::pin(actix_rt::signal::ctrl_c()), } } @@ -58,9 +82,9 @@ impl Signals { use actix_rt::signal::unix; let sig_map = [ - (unix::SignalKind::interrupt(), SignalKind::Int), - (unix::SignalKind::terminate(), SignalKind::Term), - (unix::SignalKind::quit(), SignalKind::Quit), + (unix::SignalKind::interrupt(), SignalKind::OsInt), + (unix::SignalKind::terminate(), SignalKind::OsTerm), + (unix::SignalKind::quit(), SignalKind::OsQuit), ]; let signals = sig_map @@ -68,35 +92,33 @@ impl Signals { .filter_map(|(kind, sig)| { unix::signal(*kind) .map(|tokio_sig| (*sig, tokio_sig)) - .map_err(|e| { - log::error!( - "Can not initialize stream handler for {:?} err: {}", - sig, - e + .map_err(|err| { + tracing::error!( + "can not initialize stream handler for {sig:?} err: {err}", ) }) .ok() }) .collect::>(); - Signals { signals } + OsSignals { signals } } } } -impl Future for Signals { +impl Future for OsSignals { type Output = SignalKind; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { #[cfg(not(unix))] { - self.signals.as_mut().poll(cx).map(|_| SignalKind::Int) + self.signals.as_mut().poll(cx).map(|_| SignalKind::OsInt) } #[cfg(unix)] { for (sig, fut) in self.signals.iter_mut() { - if Pin::new(fut).poll_recv(cx).is_ready() { + if fut.poll_recv(cx).is_ready() { trace!("{} received", sig); return Poll::Ready(*sig); } @@ -106,3 +128,10 @@ impl Future for Signals { } } } + +#[cfg(test)] +mod tests { + use super::*; + + static_assertions::assert_impl_all!(StopSignal: Send, Unpin); +} diff --git a/actix-server/src/socket.rs b/actix-server/src/socket.rs index 85cfb143..3d7c1d39 100644 --- a/actix-server/src/socket.rs +++ b/actix-server/src/socket.rs @@ -6,13 +6,13 @@ use std::{fmt, io}; use actix_rt::net::TcpStream; pub(crate) use mio::net::TcpListener as MioTcpListener; use mio::{event::Source, Interest, Registry, Token}; - #[cfg(unix)] pub(crate) use { - mio::net::UnixListener as MioUnixListener, - std::os::unix::net::UnixListener as StdUnixListener, + mio::net::UnixListener as MioUnixListener, std::os::unix::net::UnixListener as StdUnixListener, }; +use crate::builder::MpTcp; + pub(crate) enum MioListener { Tcp(MioTcpListener), #[cfg(unix)] @@ -95,9 +95,9 @@ impl From for MioListener { impl fmt::Debug for MioListener { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - MioListener::Tcp(ref lst) => write!(f, "{:?}", lst), - #[cfg(all(unix))] - MioListener::Uds(ref lst) => write!(f, "{:?}", lst), + MioListener::Tcp(ref lst) => write!(f, "{lst:?}"), + #[cfg(unix)] + MioListener::Uds(ref lst) => write!(f, "{lst:?}"), } } } @@ -105,9 +105,9 @@ impl fmt::Debug for MioListener { impl fmt::Display for MioListener { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - MioListener::Tcp(ref lst) => write!(f, "{:?}", lst), + MioListener::Tcp(ref lst) => write!(f, "{lst:?}"), #[cfg(unix)] - MioListener::Uds(ref lst) => write!(f, "{:?}", lst), + MioListener::Uds(ref lst) => write!(f, "{lst:?}"), } } } @@ -116,16 +116,16 @@ pub(crate) enum SocketAddr { Unknown, Tcp(StdSocketAddr), #[cfg(unix)] - Uds(mio::net::SocketAddr), + Uds(std::os::unix::net::SocketAddr), } impl fmt::Display for SocketAddr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Self::Unknown => write!(f, "Unknown SocketAddr"), - Self::Tcp(ref addr) => write!(f, "{}", addr), + Self::Tcp(ref addr) => write!(f, "{addr}"), #[cfg(unix)] - Self::Uds(ref addr) => write!(f, "{:?}", addr), + Self::Uds(ref addr) => write!(f, "{addr:?}"), } } } @@ -134,9 +134,9 @@ impl fmt::Debug for SocketAddr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Self::Unknown => write!(f, "Unknown SocketAddr"), - Self::Tcp(ref addr) => write!(f, "{:?}", addr), + Self::Tcp(ref addr) => write!(f, "{addr:?}"), #[cfg(unix)] - Self::Uds(ref addr) => write!(f, "{:?}", addr), + Self::Uds(ref addr) => write!(f, "{addr:?}"), } } } @@ -150,6 +150,7 @@ pub enum MioStream { /// Helper trait for converting a Mio stream into a Tokio stream. pub trait FromStream: Sized { + /// Creates stream from a `mio` stream. fn from_mio(sock: MioStream) -> io::Result; } @@ -215,10 +216,30 @@ mod unix_impl { pub(crate) fn create_mio_tcp_listener( addr: StdSocketAddr, backlog: u32, + mptcp: &MpTcp, ) -> io::Result { use socket2::{Domain, Protocol, Socket, Type}; - let socket = Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP))?; + #[cfg(not(target_os = "linux"))] + let protocol = Protocol::TCP; + #[cfg(target_os = "linux")] + let protocol = if matches!(mptcp, MpTcp::Disabled) { + Protocol::TCP + } else { + Protocol::MPTCP + }; + + let socket = match Socket::new(Domain::for_address(addr), Type::STREAM, Some(protocol)) { + Ok(sock) => sock, + + Err(err) if matches!(mptcp, MpTcp::TcpFallback) => { + tracing::warn!("binding socket as MPTCP failed: {err}"); + tracing::warn!("falling back to TCP"); + Socket::new(Domain::for_address(addr), Type::STREAM, Some(Protocol::TCP))? + } + + Err(err) => return Err(err), + }; socket.set_reuse_address(true)?; socket.set_nonblocking(true)?; @@ -235,14 +256,14 @@ mod tests { #[test] fn socket_addr() { let addr = SocketAddr::Tcp("127.0.0.1:8080".parse().unwrap()); - assert!(format!("{:?}", addr).contains("127.0.0.1:8080")); - assert_eq!(format!("{}", addr), "127.0.0.1:8080"); + assert!(format!("{addr:?}").contains("127.0.0.1:8080")); + assert_eq!(format!("{addr}"), "127.0.0.1:8080"); let addr: StdSocketAddr = "127.0.0.1:0".parse().unwrap(); - let lst = create_mio_tcp_listener(addr, 128).unwrap(); + let lst = create_mio_tcp_listener(addr, 128, &MpTcp::Disabled).unwrap(); let lst = MioListener::Tcp(lst); - assert!(format!("{:?}", lst).contains("TcpListener")); - assert!(format!("{}", lst).contains("127.0.0.1")); + assert!(format!("{lst:?}").contains("TcpListener")); + assert!(format!("{lst}").contains("127.0.0.1")); } #[test] @@ -252,12 +273,12 @@ mod tests { if let Ok(socket) = MioUnixListener::bind("/tmp/sock.xxxxx") { let addr = socket.local_addr().expect("Couldn't get local address"); let a = SocketAddr::Uds(addr); - assert!(format!("{:?}", a).contains("/tmp/sock.xxxxx")); - assert!(format!("{}", a).contains("/tmp/sock.xxxxx")); + assert!(format!("{a:?}").contains("/tmp/sock.xxxxx")); + assert!(format!("{a}").contains("/tmp/sock.xxxxx")); let lst = MioListener::Uds(socket); - assert!(format!("{:?}", lst).contains("/tmp/sock.xxxxx")); - assert!(format!("{}", lst).contains("/tmp/sock.xxxxx")); + assert!(format!("{lst:?}").contains("/tmp/sock.xxxxx")); + assert!(format!("{lst}").contains("/tmp/sock.xxxxx")); } } } diff --git a/actix-server/src/test_server.rs b/actix-server/src/test_server.rs index 67659948..5326874f 100644 --- a/actix-server/src/test_server.rs +++ b/actix-server/src/test_server.rs @@ -117,13 +117,15 @@ impl TestServerHandle { /// Stop server. fn stop(&mut self) { - let _ = self.server_handle.stop(false); + drop(self.server_handle.stop(false)); self.thread_handle.take().unwrap().join().unwrap().unwrap(); } /// Connect to server, returning a Tokio `TcpStream`. pub fn connect(&self) -> io::Result { - TcpStream::from_std(net::TcpStream::connect(self.addr)?) + let stream = net::TcpStream::connect(self.addr)?; + stream.set_nonblocking(true)?; + TcpStream::from_std(stream) } } diff --git a/actix-server/src/waker_queue.rs b/actix-server/src/waker_queue.rs index a7280901..b6d4e5b0 100644 --- a/actix-server/src/waker_queue.rs +++ b/actix-server/src/waker_queue.rs @@ -52,7 +52,7 @@ impl WakerQueue { waker .wake() - .unwrap_or_else(|e| panic!("can not wake up Accept Poll: {}", e)); + .unwrap_or_else(|err| panic!("can not wake up Accept Poll: {err}")); } /// Get a MutexGuard of the waker queue. @@ -62,7 +62,7 @@ impl WakerQueue { /// Reset the waker queue so it does not grow infinitely. pub(crate) fn reset(queue: &mut VecDeque) { - std::mem::swap(&mut VecDeque::::with_capacity(16), queue); + *queue = VecDeque::::with_capacity(16); } } diff --git a/actix-server/src/worker.rs b/actix-server/src/worker.rs index 2fef9a7b..dcecda0e 100644 --- a/actix-server/src/worker.rs +++ b/actix-server/src/worker.rs @@ -1,6 +1,7 @@ use std::{ future::Future, io, mem, + num::NonZeroUsize, pin::Pin, rc::Rc, sync::{ @@ -17,11 +18,11 @@ use actix_rt::{ Arbiter, ArbiterHandle, System, }; use futures_core::{future::LocalBoxFuture, ready}; -use log::{error, info, trace}; use tokio::sync::{ mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, oneshot, }; +use tracing::{error, info, trace}; use crate::{ service::{BoxedServerService, InternalServiceFactory}, @@ -249,8 +250,11 @@ pub(crate) struct ServerWorkerConfig { impl Default for ServerWorkerConfig { fn default() -> Self { - // 512 is the default max blocking thread count of tokio runtime. - let max_blocking_threads = std::cmp::max(512 / num_cpus::get_physical(), 1); + let parallelism = std::thread::available_parallelism().map_or(2, NonZeroUsize::get); + + // 512 is the default max blocking thread count of a Tokio runtime. + let max_blocking_threads = std::cmp::max(512 / parallelism, 1); + Self { shutdown_timeout: Duration::from_secs(30), max_blocking_threads, @@ -321,7 +325,7 @@ impl ServerWorker { // no actix system (None, Some(rt_handle)) => { std::thread::Builder::new() - .name(format!("actix-server worker {}", idx)) + .name(format!("actix-server worker {idx}")) .spawn(move || { let (worker_stopped_tx, worker_stopped_rx) = oneshot::channel(); @@ -337,11 +341,10 @@ impl ServerWorker { Ok((token, svc)) => services.push((idx, token, svc)), Err(err) => { - error!("Can not start worker: {:?}", err); - return Err(io::Error::new( - io::ErrorKind::Other, - format!("can not start server service {}", idx), - )); + error!("can not start worker: {err:?}"); + return Err(io::Error::other(format!( + "can not start server service {idx}", + ))); } } } @@ -436,13 +439,12 @@ impl ServerWorker { Ok((token, svc)) => services.push((idx, token, svc)), Err(err) => { - error!("Can not start worker: {:?}", err); + error!("can not start worker: {err:?}"); Arbiter::current().stop(); factory_tx - .send(Err(io::Error::new( - io::ErrorKind::Other, - format!("can not start server service {}", idx), - ))) + .send(Err(io::Error::other(format!( + "can not start server service {idx}", + )))) .unwrap(); return; } @@ -476,7 +478,7 @@ impl ServerWorker { fn restart_service(&mut self, idx: usize, factory_id: usize) { let factory = &self.factories[factory_id]; - trace!("Service {:?} failed, restarting", factory.name(idx)); + trace!("service {:?} failed, restarting", factory.name(idx)); self.services[idx].status = WorkerServiceStatus::Restarting; self.state = WorkerState::Restarting(Restart { factory_id, @@ -508,7 +510,7 @@ impl ServerWorker { Poll::Ready(Ok(_)) => { if srv.status == WorkerServiceStatus::Unavailable { trace!( - "Service {:?} is available", + "service {:?} is available", self.factories[srv.factory_idx].name(idx) ); srv.status = WorkerServiceStatus::Available; @@ -519,7 +521,7 @@ impl ServerWorker { if srv.status == WorkerServiceStatus::Available { trace!( - "Service {:?} is unavailable", + "service {:?} is unavailable", self.factories[srv.factory_idx].name(idx) ); srv.status = WorkerServiceStatus::Unavailable; @@ -527,7 +529,7 @@ impl ServerWorker { } Poll::Ready(Err(_)) => { error!( - "Service {:?} readiness check returned error, restarting", + "service {:?} readiness check returned error, restarting", self.factories[srv.factory_idx].name(idx) ); srv.status = WorkerServiceStatus::Failed; @@ -585,16 +587,14 @@ impl Future for ServerWorker { let this = self.as_mut().get_mut(); // `StopWorker` message handler - if let Poll::Ready(Some(Stop { graceful, tx })) = - Pin::new(&mut this.stop_rx).poll_recv(cx) - { + if let Poll::Ready(Some(Stop { graceful, tx })) = this.stop_rx.poll_recv(cx) { let num = this.counter.total(); if num == 0 { - info!("Shutting down idle worker"); + info!("shutting down idle worker"); let _ = tx.send(true); return Poll::Ready(()); } else if graceful { - info!("Graceful worker shutdown; finishing {} connections", num); + info!("graceful worker shutdown; finishing {} connections", num); this.shutdown(false); this.state = WorkerState::Shutdown(Shutdown { @@ -603,7 +603,7 @@ impl Future for ServerWorker { tx, }); } else { - info!("Force shutdown worker, closing {} connections", num); + info!("force shutdown worker, closing {} connections", num); this.shutdown(true); let _ = tx.send(false); @@ -623,12 +623,13 @@ impl Future for ServerWorker { self.poll(cx) } }, + WorkerState::Restarting(ref mut restart) => { let factory_id = restart.factory_id; let token = restart.token; - let (token_new, service) = ready!(restart.fut.as_mut().poll(cx)) - .unwrap_or_else(|_| { + let (token_new, service) = + ready!(restart.fut.as_mut().poll(cx)).unwrap_or_else(|_| { panic!( "Can not restart {:?} service", this.factories[factory_id].name(token) @@ -638,7 +639,7 @@ impl Future for ServerWorker { assert_eq!(token, token_new); trace!( - "Service {:?} has been restarted", + "service {:?} has been restarted", this.factories[factory_id].name(token) ); @@ -647,9 +648,10 @@ impl Future for ServerWorker { self.poll(cx) } + WorkerState::Shutdown(ref mut shutdown) => { // drop all pending connections in rx channel. - while let Poll::Ready(Some(conn)) = Pin::new(&mut this.conn_rx).poll_recv(cx) { + while let Poll::Ready(Some(conn)) = this.conn_rx.poll_recv(cx) { // WorkerCounterGuard is needed as Accept thread has incremented counter. // It's guard's job to decrement the counter together with drop of Conn. let guard = this.counter.guard(); @@ -680,12 +682,13 @@ impl Future for ServerWorker { shutdown.timer.as_mut().poll(cx) } } + // actively poll stream and handle worker command WorkerState::Available => loop { match this.check_readiness(cx) { Ok(true) => {} Ok(false) => { - trace!("Worker is unavailable"); + trace!("worker is unavailable"); this.state = WorkerState::Unavailable; return self.poll(cx); } @@ -696,10 +699,13 @@ impl Future for ServerWorker { } // handle incoming io stream - match ready!(Pin::new(&mut this.conn_rx).poll_recv(cx)) { + match ready!(this.conn_rx.poll_recv(cx)) { Some(msg) => { let guard = this.counter.guard(); - let _ = this.services[msg.token].service.call((guard, msg.io)); + let _ = this.services[msg.token] + .service + .call((guard, msg.io)) + .into_inner(); } None => return Poll::Ready(()), }; @@ -708,9 +714,7 @@ impl Future for ServerWorker { } } -fn wrap_worker_services( - services: Vec<(usize, usize, BoxedServerService)>, -) -> Vec { +fn wrap_worker_services(services: Vec<(usize, usize, BoxedServerService)>) -> Vec { services .into_iter() .fold(Vec::new(), |mut services, (idx, token, service)| { diff --git a/actix-server/tests/server.rs b/actix-server/tests/server.rs index 0a70402b..fa1225f1 100644 --- a/actix-server/tests/server.rs +++ b/actix-server/tests/server.rs @@ -1,3 +1,5 @@ +#![allow(clippy::let_underscore_future, missing_docs)] + use std::{ net, sync::{ @@ -186,9 +188,9 @@ fn test_start() { #[actix_rt::test] async fn test_max_concurrent_connections() { // Note: - // A tcp listener would accept connects based on it's backlog setting. + // A TCP listener would accept connects based on it's backlog setting. // - // The limit test on the other hand is only for concurrent tcp stream limiting a work + // The limit test on the other hand is only for concurrent TCP stream limiting a work // thread accept. use tokio::io::AsyncWriteExt; @@ -254,6 +256,7 @@ async fn test_max_concurrent_connections() { h.join().unwrap().unwrap(); } +// TODO: race-y failures detected due to integer underflow when calling Counter::total #[actix_rt::test] async fn test_service_restart() { use std::task::{Context, Poll}; diff --git a/actix-server/tests/testing_server.rs b/actix-server/tests/testing_server.rs index 921caa05..2b5b5418 100644 --- a/actix-server/tests/testing_server.rs +++ b/actix-server/tests/testing_server.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs)] + use std::net; use actix_rt::net::TcpStream; @@ -69,5 +71,7 @@ async fn new_with_builder() { srv.connect().unwrap(); // connect to alt service defined in custom ServerBuilder - TcpStream::from_std(net::TcpStream::connect(alt_addr).unwrap()).unwrap(); + let stream = net::TcpStream::connect(alt_addr).unwrap(); + stream.set_nonblocking(true).unwrap(); + TcpStream::from_std(stream).unwrap(); } diff --git a/actix-service/CHANGES.md b/actix-service/CHANGES.md index 4e46148f..35ded6aa 100644 --- a/actix-service/CHANGES.md +++ b/actix-service/CHANGES.md @@ -1,35 +1,42 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased +- Minimum supported Rust version (MSRV) is now 1.88. +- internal: Use `core::future::{ready, Ready}` instead of hand-crafted one + +## 2.0.3 + +- Minimum supported Rust version (MSRV) is now 1.71. + +## 2.0.2 -## 2.0.2 - 2021-12-18 - Service types can now be `Send` and `'static` regardless of request, response, and config types, etc. [#397] [#397]: https://github.com/actix/actix-net/pull/397 +## 2.0.1 -## 2.0.1 - 2021-10-11 - Documentation fix. [#388] [#388]: https://github.com/actix/actix-net/pull/388 +## 2.0.0 -## 2.0.0 - 2021-04-16 - Removed pipeline and related structs/functions. [#335] [#335]: https://github.com/actix/actix-net/pull/335 +## 2.0.0-beta.5 -## 2.0.0-beta.5 - 2021-03-15 - Add default `Service` trait impl for `Rc` and `&S: Service`. [#288] - Add `boxed::rc_service` function for constructing `boxed::RcService` type [#290] [#288]: https://github.com/actix/actix-net/pull/288 [#290]: https://github.com/actix/actix-net/pull/290 +## 2.0.0-beta.4 -## 2.0.0-beta.4 - 2021-02-04 - `Service::poll_ready` and `Service::call` receive `&self`. [#247] - `apply_fn` and `apply_fn_factory` now receive `Fn(Req, &Service)` function type. [#247] - `apply_cfg` and `apply_cfg_factory` now receive `Fn(Req, &Service)` function type. [#247] @@ -37,158 +44,153 @@ [#247]: https://github.com/actix/actix-net/pull/247 +## 2.0.0-beta.3 -## 2.0.0-beta.3 - 2021-01-09 - The `forward_ready!` macro converts errors. [#246] [#246]: https://github.com/actix/actix-net/pull/246 +## 2.0.0-beta.2 -## 2.0.0-beta.2 - 2021-01-03 - Remove redundant type parameter from `map_config`. +## 2.0.0-beta.1 -## 2.0.0-beta.1 - 2020-12-28 -- `Service`, other traits, and many type signatures now take the the request type as a type - parameter instead of an associated type. [#232] +- `Service`, other traits, and many type signatures now take the the request type as a type parameter instead of an associated type. [#232] - Add `always_ready!` and `forward_ready!` macros. [#233] - Crate is now `no_std`. [#233] - Migrate pin projections to `pin-project-lite`. [#233] -- Remove `AndThenApplyFn` and Pipeline `and_then_apply_fn`. Use the - `.and_then(apply_fn(...))` construction. [#233] +- Remove `AndThenApplyFn` and Pipeline `and_then_apply_fn`. Use the `.and_then(apply_fn(...))` construction. [#233] - Move non-vital methods to `ServiceExt` and `ServiceFactoryExt` extension traits. [#235] [#232]: https://github.com/actix/actix-net/pull/232 [#233]: https://github.com/actix/actix-net/pull/233 [#235]: https://github.com/actix/actix-net/pull/235 +## 1.0.6 -## 1.0.6 - 2020-08-09 -- Removed unsound custom Cell implementation that allowed obtaining several mutable references to - the same data, which is undefined behavior in Rust and could lead to violations of memory safety. External code could obtain several mutable references to the same data through - service combinators. Attempts to acquire several mutable references to the same data will instead - result in a panic. +- Removed unsound custom Cell implementation that allowed obtaining several mutable references to the same data, which is undefined behavior in Rust and could lead to violations of memory safety. External code could obtain several mutable references to the same data through service combinators. Attempts to acquire several mutable references to the same data will instead result in a panic. +## 1.0.5 -## 1.0.5 - 2020-01-16 - Fixed unsoundness in .and_then()/.then() service combinators. +## 1.0.4 -## 1.0.4 - 2020-01-15 - Revert 1.0.3 change +## 1.0.3 -## 1.0.3 - 2020-01-15 - Fixed unsoundness in `AndThenService` impl. +## 1.0.2 -## 1.0.2 - 2020-01-08 - Add `into_service` helper function. +## 1.0.1 -## 1.0.1 - 2019-12-22 - `map_config()` and `unit_config()` now accept `IntoServiceFactory` type. +## 1.0.0 -## 1.0.0 - 2019-12-11 - Add Clone impl for Apply service +## 1.0.0-alpha.4 -## 1.0.0-alpha.4 - 2019-12-08 - Renamed `service_fn` to `fn_service` - Renamed `factory_fn` to `fn_factory` - Renamed `factory_fn_cfg` to `fn_factory_with_config` +## 1.0.0-alpha.3 -## 1.0.0-alpha.3 - 2019-12-06 - Add missing Clone impls - Restore `Transform::map_init_err()` combinator - Restore `Service/Factory::apply_fn()` in form of `Pipeline/Factory::and_then_apply_fn()` - Optimize service combinators and futures memory layout +## 1.0.0-alpha.2 -## 1.0.0-alpha.2 - 2019-12-02 - Use owned config value for service factory - Renamed BoxedNewService/BoxedService to BoxServiceFactory/BoxService +## 1.0.0-alpha.1 -## 1.0.0-alpha.1 - 2019-11-25 - Migrated to `std::future` - `NewService` renamed to `ServiceFactory` - Added `pipeline` and `pipeline_factory` function +## 0.4.2 -## 0.4.2 - 2019-08-27 - Check service readiness for `new_apply_cfg` combinator +## 0.4.1 -## 0.4.1 - 2019-06-06 - Add `new_apply_cfg` function +## 0.4.0 -## 0.4.0 - 2019-05-12 - Add `NewService::map_config` and `NewService::unit_config` combinators. - Use associated type for `NewService` config. - Change `apply_cfg` function. - Renamed helper functions. +## 0.3.6 -## 0.3.6 - 2019-04-07 - Poll boxed service call result immediately +## 0.3.5 -## 0.3.5 - 2019-03-29 - Add `impl Service for Rc>`. +## 0.3.4 -## 0.3.4 - 2019-03-12 - Add `Transform::from_err()` combinator - Add `apply_fn` helper - Add `apply_fn_factory` helper - Add `apply_transform` helper - Add `apply_cfg` helper +## 0.3.3 -## 0.3.3 - 2019-03-09 - Add `ApplyTransform` new service for transform and new service. - Add `NewService::apply_cfg()` combinator, allows to use nested `NewService` with different config parameter. - Revert IntoFuture change +## 0.3.2 -## 0.3.2 - 2019-03-04 - Change `NewService::Future` and `Transform::Future` to the `IntoFuture` trait. - Export `AndThenTransform` type +## 0.3.1 -## 0.3.1 - 2019-03-04 - Simplify Transform trait +## 0.3.0 -## 0.3.0 - 2019-03-02 - Added boxed NewService and Service. - Added `Config` parameter to `NewService` trait. - Added `Config` parameter to `NewTransform` trait. +## 0.2.2 -## 0.2.2 - 2019-02-19 - Added `NewService` impl for `Rc where S: NewService` - Added `NewService` impl for `Arc where S: NewService` +## 0.2.1 -## 0.2.1 - 2019-02-03 - Generalize `.apply` combinator with Transform trait +## 0.2.0 -## 0.2.0 - 2019-02-01 - Use associated type instead of generic for Service definition. - * Before: + - Before: ```rust impl Service for Client { type Response = Response; // ... } ``` - * After: + - After: ```rust impl Service for Client { type Request = Request; @@ -197,31 +199,31 @@ } ``` +## 0.1.6 -## 0.1.6 - 2019-01-24 - Use `FnMut` instead of `Fn` for .apply() and .map() combinators and `FnService` type - Change `.apply()` error semantic, new service's error is `From` +## 0.1.5 -## 0.1.5 - 2019-01-13 - Make `Out::Error` convertible from `T::Error` for apply combinator +## 0.1.4 -## 0.1.4 - 2019-01-11 - Use `FnMut` instead of `Fn` for `FnService` +## 0.1.3 -## 0.1.3 - 2018-12-12 - Split service combinators to separate trait +## 0.1.2 -## 0.1.2 - 2018-12-12 - Release future early for `.and_then()` and `.then()` combinators +## 0.1.1 -## 0.1.1 - 2018-12-09 - Added Service impl for `Box` +## 0.1.0 -## 0.1.0 - 2018-12-09 - Initial import diff --git a/actix-service/Cargo.toml b/actix-service/Cargo.toml index d4b602fd..bc9d1acf 100644 --- a/actix-service/Cargo.toml +++ b/actix-service/Cargo.toml @@ -1,28 +1,23 @@ [package] name = "actix-service" -version = "2.0.2" -authors = [ - "Nikolay Kim ", - "Rob Ede ", - "fakeshadow <24548779@qq.com>", -] +version = "2.0.3" +authors = ["Nikolay Kim ", "Rob Ede "] description = "Service trait and combinators for representing asynchronous request/response operations." keywords = ["network", "framework", "async", "futures", "service"] categories = ["network-programming", "asynchronous", "no-std"] repository = "https://github.com/actix/actix-net" license = "MIT OR Apache-2.0" -edition = "2018" - -[lib] -name = "actix_service" -path = "src/lib.rs" +edition.workspace = true +rust-version.workspace = true [dependencies] -futures-core = { version = "0.3.7", default-features = false } -paste = "1" +futures-core = { version = "0.3.17", default-features = false } pin-project-lite = "0.2" [dev-dependencies] -actix-rt = "2.0.0" -actix-utils = "3.0.0" -futures-util = { version = "0.3.7", default-features = false } +actix-rt = "2" +actix-utils = "3" +futures-util = { version = "0.3.17", default-features = false } + +[lints] +workspace = true diff --git a/actix-service/README.md b/actix-service/README.md index 62f9fe1c..7a31fdc6 100644 --- a/actix-service/README.md +++ b/actix-service/README.md @@ -3,10 +3,10 @@ > Service trait and combinators for representing asynchronous request/response operations. [![crates.io](https://img.shields.io/crates/v/actix-service?label=latest)](https://crates.io/crates/actix-service) -[![Documentation](https://docs.rs/actix-service/badge.svg?version=2.0.2)](https://docs.rs/actix-service/2.0.2) -[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) -![License](https://img.shields.io/crates/l/actix-service.svg) -[![Dependency Status](https://deps.rs/crate/actix-service/2.0.2/status.svg)](https://deps.rs/crate/actix-service/2.0.2) +[![Documentation](https://docs.rs/actix-service/badge.svg?version=2.0.3)](https://docs.rs/actix-service/2.0.3) +[![Version](https://img.shields.io/badge/rustc-1.88+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-service.svg) +[![Dependency Status](https://deps.rs/crate/actix-service/2.0.3/status.svg)](https://deps.rs/crate/actix-service/2.0.3) ![Download](https://img.shields.io/crates/d/actix-service.svg) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-service/examples/clone.rs b/actix-service/examples/clone.rs index 1f61a648..bc7ff1cb 100644 --- a/actix-service/examples/clone.rs +++ b/actix-service/examples/clone.rs @@ -1,3 +1,5 @@ +#![allow(missing_docs)] + use std::{future::Future, sync::mpsc, time::Duration}; async fn oracle(f: F) -> (u32, u32) diff --git a/actix-service/src/and_then.rs b/actix-service/src/and_then.rs index 38980079..e453f7be 100644 --- a/actix-service/src/and_then.rs +++ b/actix-service/src/and_then.rs @@ -121,12 +121,7 @@ pub struct AndThenServiceFactory where A: ServiceFactory, A::Config: Clone, - B: ServiceFactory< - A::Response, - Config = A::Config, - Error = A::Error, - InitError = A::InitError, - >, + B: ServiceFactory, { inner: Rc<(A, B)>, _phantom: PhantomData, @@ -136,12 +131,7 @@ impl AndThenServiceFactory where A: ServiceFactory, A::Config: Clone, - B: ServiceFactory< - A::Response, - Config = A::Config, - Error = A::Error, - InitError = A::InitError, - >, + B: ServiceFactory, { /// Create new `AndThenFactory` combinator pub(crate) fn new(a: A, b: B) -> Self { @@ -156,12 +146,7 @@ impl ServiceFactory for AndThenServiceFactory where A: ServiceFactory, A::Config: Clone, - B: ServiceFactory< - A::Response, - Config = A::Config, - Error = A::Error, - InitError = A::InitError, - >, + B: ServiceFactory, { type Response = B::Response; type Error = A::Error; @@ -184,12 +169,7 @@ impl Clone for AndThenServiceFactory where A: ServiceFactory, A::Config: Clone, - B: ServiceFactory< - A::Response, - Config = A::Config, - Error = A::Error, - InitError = A::InitError, - >, + B: ServiceFactory, { fn clone(&self) -> Self { Self { @@ -266,15 +246,16 @@ mod tests { use alloc::rc::Rc; use core::{ cell::Cell, + future::{ready, Ready}, task::{Context, Poll}, }; use futures_util::future::lazy; use crate::{ - fn_factory, ok, + fn_factory, pipeline::{pipeline, pipeline_factory}, - ready, Ready, Service, ServiceFactory, + Service, ServiceFactory, }; struct Srv1(Rc>); @@ -290,7 +271,7 @@ mod tests { } fn call(&self, req: &'static str) -> Self::Future { - ok(req) + ready(Ok(req)) } } @@ -308,7 +289,7 @@ mod tests { } fn call(&self, req: &'static str) -> Self::Future { - ok((req, "srv2")) + ready(Ok((req, "srv2"))) } } @@ -334,9 +315,8 @@ mod tests { async fn test_new_service() { let cnt = Rc::new(Cell::new(0)); let cnt2 = cnt.clone(); - let new_srv = - pipeline_factory(fn_factory(move || ready(Ok::<_, ()>(Srv1(cnt2.clone()))))) - .and_then(move || ready(Ok(Srv2(cnt.clone())))); + let new_srv = pipeline_factory(fn_factory(move || ready(Ok::<_, ()>(Srv1(cnt2.clone()))))) + .and_then(move || ready(Ok(Srv2(cnt.clone())))); let srv = new_srv.new_service(()).await.unwrap(); let res = srv.call("srv1").await; diff --git a/actix-service/src/apply.rs b/actix-service/src/apply.rs index c77f4242..2bf88637 100644 --- a/actix-service/src/apply.rs +++ b/actix-service/src/apply.rs @@ -140,8 +140,7 @@ where } } -impl ServiceFactory - for ApplyFactory +impl ServiceFactory for ApplyFactory where SF: ServiceFactory, F: Fn(Req, &SF::Service) -> Fut + Clone, @@ -209,16 +208,12 @@ where #[cfg(test)] mod tests { - use core::task::Poll; + use core::future::{ready, Ready}; use futures_util::future::lazy; use super::*; - use crate::{ - ok, - pipeline::{pipeline, pipeline_factory}, - Ready, Service, ServiceFactory, - }; + use crate::pipeline::{pipeline, pipeline_factory}; #[derive(Clone)] struct Srv; @@ -231,7 +226,7 @@ mod tests { crate::always_ready!(); fn call(&self, _: ()) -> Self::Future { - ok(()) + ready(Ok(())) } } @@ -255,7 +250,7 @@ mod tests { #[actix_rt::test] async fn test_new_service() { let new_srv = pipeline_factory(apply_fn_factory( - || ok::<_, ()>(Srv), + || ready(Ok::<_, ()>(Srv)), |req: &'static str, srv| { let fut = srv.call(()); async move { diff --git a/actix-service/src/apply_cfg.rs b/actix-service/src/apply_cfg.rs index 25fc5fc2..028fb317 100644 --- a/actix-service/src/apply_cfg.rs +++ b/actix-service/src/apply_cfg.rs @@ -198,8 +198,7 @@ pin_project! { } } -impl Future - for ApplyConfigServiceFactoryResponse +impl Future for ApplyConfigServiceFactoryResponse where SF: ServiceFactory, SF::InitError: From, diff --git a/actix-service/src/boxed.rs b/actix-service/src/boxed.rs index 3141c5e4..e3f7a80b 100644 --- a/actix-service/src/boxed.rs +++ b/actix-service/src/boxed.rs @@ -3,36 +3,38 @@ use alloc::{boxed::Box, rc::Rc}; use core::{future::Future, pin::Pin}; -use paste::paste; - use crate::{Service, ServiceFactory}; /// A boxed future with no send bound or lifetime parameters. pub type BoxFuture = Pin>>; -macro_rules! service_object { - ($name: ident, $type: tt, $fn_name: ident) => { - paste! { - #[doc = "Type alias for service trait object using `" $type "`."] - pub type $name = $type< - dyn Service>>, - >; +/// Type alias for service trait object using [`Box`]. +pub type BoxService = + Box>>>; - #[doc = "Wraps service as a trait object using [`" $name "`]."] - pub fn $fn_name(service: S) -> $name - where - S: Service + 'static, - Req: 'static, - S::Future: 'static, - { - $type::new(ServiceWrapper::new(service)) - } - } - }; +/// Wraps service as a trait object using [`BoxService`]. +pub fn service(service: S) -> BoxService +where + S: Service + 'static, + Req: 'static, + S::Future: 'static, +{ + Box::new(ServiceWrapper::new(service)) } -service_object!(BoxService, Box, service); -service_object!(RcService, Rc, rc_service); +/// Type alias for service trait object using [`Rc`]. +pub type RcService = + Rc>>>; + +/// Wraps service as a trait object using [`RcService`]. +pub fn rc_service(service: S) -> RcService +where + S: Service + 'static, + Req: 'static, + S::Future: 'static, +{ + Rc::new(ServiceWrapper::new(service)) +} struct ServiceWrapper { inner: S, @@ -91,8 +93,7 @@ type Inner = Box< >, >; -impl ServiceFactory - for BoxServiceFactory +impl ServiceFactory for BoxServiceFactory where Req: 'static, Res: 'static, diff --git a/actix-service/src/ext.rs b/actix-service/src/ext.rs index f5fe6ed1..5cf612de 100644 --- a/actix-service/src/ext.rs +++ b/actix-service/src/ext.rs @@ -44,7 +44,7 @@ pub trait ServiceExt: Service { /// Call another service after call to this one has resolved successfully. /// /// This function can be used to chain two services together and ensure that the second service - /// isn't called until call to the fist service have finished. Result of the call to the first + /// isn't called until call to the first service have finished. Result of the call to the first /// service is used as an input parameter for the second service's call. /// /// Note that this function consumes the receiving service and returns a wrapped version of it. diff --git a/actix-service/src/fn_service.rs b/actix-service/src/fn_service.rs index ae22e39b..b4d82013 100644 --- a/actix-service/src/fn_service.rs +++ b/actix-service/src/fn_service.rs @@ -1,11 +1,12 @@ -use core::{future::Future, marker::PhantomData}; +use core::{ + future::{ready, Future, Ready}, + marker::PhantomData, +}; -use crate::{ok, IntoService, IntoServiceFactory, Ready, Service, ServiceFactory}; +use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory}; /// Create `ServiceFactory` for function that can act as a `Service` -pub fn fn_service( - f: F, -) -> FnServiceFactory +pub fn fn_service(f: F) -> FnServiceFactory where F: Fn(Req) -> Fut + Clone, Fut: Future>, @@ -48,9 +49,7 @@ where /// Ok(()) /// } /// ``` -pub fn fn_factory( - f: F, -) -> FnServiceNoConfig +pub fn fn_factory(f: F) -> FnServiceNoConfig where F: Fn() -> Fut, Fut: Future>, @@ -214,7 +213,7 @@ where type Future = Ready>; fn new_service(&self, _: Cfg) -> Self::Future { - ok(FnService::new(self.f.clone())) + ready(Ok(FnService::new(self.f.clone()))) } } @@ -265,8 +264,7 @@ where } } -impl ServiceFactory - for FnServiceConfig +impl ServiceFactory for FnServiceConfig where F: Fn(Cfg) -> Fut, Fut: Future>, @@ -351,16 +349,15 @@ where #[cfg(test)] mod tests { - use core::task::Poll; + use core::{future::ready, task::Poll}; use futures_util::future::lazy; use super::*; - use crate::{ok, Service, ServiceFactory}; #[actix_rt::test] async fn test_fn_service() { - let new_srv = fn_service(|()| ok::<_, ()>("srv")); + let new_srv = fn_service(|()| ready(Ok::<_, ()>("srv"))); let srv = new_srv.new_service(()).await.unwrap(); let res = srv.call(()).await; @@ -371,7 +368,7 @@ mod tests { #[actix_rt::test] async fn test_fn_service_service() { - let srv = fn_service(|()| ok::<_, ()>("srv")); + let srv = fn_service(|()| ready(Ok::<_, ()>("srv"))); let res = srv.call(()).await; assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(()))); @@ -382,7 +379,9 @@ mod tests { #[actix_rt::test] async fn test_fn_service_with_config() { let new_srv = fn_factory_with_config(|cfg: usize| { - ok::<_, ()>(fn_service(move |()| ok::<_, ()>(("srv", cfg)))) + ready(Ok::<_, ()>(fn_service(move |()| { + ready(Ok::<_, ()>(("srv", cfg))) + }))) }); let srv = new_srv.new_service(1).await.unwrap(); @@ -394,17 +393,22 @@ mod tests { #[actix_rt::test] async fn test_auto_impl_send() { - use crate::{map_config, ServiceExt, ServiceFactoryExt}; use alloc::rc::Rc; - let srv_1 = fn_service(|_: Rc| ok::<_, Rc>(Rc::new(0u8))); + use crate::{map_config, ServiceExt, ServiceFactoryExt}; + + let srv_1 = fn_service(|_: Rc| ready(Ok::<_, Rc>(Rc::new(0u8)))); let fac_1 = fn_factory_with_config(|_: Rc| { - ok::<_, Rc>(fn_service(|_: Rc| ok::<_, Rc>(Rc::new(0u8)))) + ready(Ok::<_, Rc>(fn_service(|_: Rc| { + ready(Ok::<_, Rc>(Rc::new(0u8))) + }))) }); let fac_2 = fn_factory(|| { - ok::<_, Rc>(fn_service(|_: Rc| ok::<_, Rc>(Rc::new(0u8)))) + ready(Ok::<_, Rc>(fn_service(|_: Rc| { + ready(Ok::<_, Rc>(Rc::new(0u8))) + }))) }); fn is_send(_: &T) {} diff --git a/actix-service/src/lib.rs b/actix-service/src/lib.rs index 96a6b164..43587842 100644 --- a/actix-service/src/lib.rs +++ b/actix-service/src/lib.rs @@ -1,8 +1,6 @@ //! See [`Service`] docs for information on this crate's foundational trait. #![no_std] -#![deny(rust_2018_idioms, nonstandard_style)] -#![warn(future_incompatible, missing_docs)] #![allow(clippy::type_complexity)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] @@ -28,20 +26,18 @@ mod map_config; mod map_err; mod map_init_err; mod pipeline; -mod ready; mod then; mod transform; mod transform_err; -pub use self::apply::{apply_fn, apply_fn_factory}; -pub use self::apply_cfg::{apply_cfg, apply_cfg_factory}; -pub use self::ext::{ServiceExt, ServiceFactoryExt, TransformExt}; -pub use self::fn_service::{fn_factory, fn_factory_with_config, fn_service}; -pub use self::map_config::{map_config, unit_config}; -pub use self::transform::{apply, ApplyTransform, Transform}; - -#[allow(unused_imports)] -use self::ready::{err, ok, ready, Ready}; +pub use self::{ + apply::{apply_fn, apply_fn_factory}, + apply_cfg::{apply_cfg, apply_cfg_factory}, + ext::{ServiceExt, ServiceFactoryExt, TransformExt}, + fn_service::{fn_factory, fn_factory_with_config, fn_service}, + map_config::{map_config, unit_config}, + transform::{apply, ApplyTransform, Transform}, +}; /// An asynchronous operation from `Request` to a `Response`. /// diff --git a/actix-service/src/macros.rs b/actix-service/src/macros.rs index 503cf116..fc46bb9d 100644 --- a/actix-service/src/macros.rs +++ b/actix-service/src/macros.rs @@ -25,6 +25,8 @@ /// } /// } /// ``` +/// +/// [`forward_ready!`]: crate::forward_ready #[macro_export] macro_rules! always_ready { () => { diff --git a/actix-service/src/map.rs b/actix-service/src/map.rs index 49ddd9fe..23dc0d08 100644 --- a/actix-service/src/map.rs +++ b/actix-service/src/map.rs @@ -199,12 +199,12 @@ where #[cfg(test)] mod tests { + use core::future::{ready, Ready}; + use futures_util::future::lazy; use super::*; - use crate::{ - ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory, ServiceFactoryExt, - }; + use crate::{IntoServiceFactory, ServiceExt, ServiceFactoryExt}; struct Srv; @@ -216,7 +216,7 @@ mod tests { crate::always_ready!(); fn call(&self, _: ()) -> Self::Future { - ok(()) + ready(Ok(())) } } @@ -237,7 +237,7 @@ mod tests { #[actix_rt::test] async fn test_new_service() { - let new_srv = (|| ok::<_, ()>(Srv)).into_factory().map(|_| "ok"); + let new_srv = (|| ready(Ok::<_, ()>(Srv))).into_factory().map(|_| "ok"); let srv = new_srv.new_service(&()).await.unwrap(); let res = srv.call(()).await; assert!(res.is_ok()); diff --git a/actix-service/src/map_err.rs b/actix-service/src/map_err.rs index 3ce6f418..e3836ed8 100644 --- a/actix-service/src/map_err.rs +++ b/actix-service/src/map_err.rs @@ -202,13 +202,12 @@ where #[cfg(test)] mod tests { + use core::future::{ready, Ready}; + use futures_util::future::lazy; use super::*; - use crate::{ - err, ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory, - ServiceFactoryExt, - }; + use crate::{IntoServiceFactory, ServiceExt, ServiceFactoryExt}; struct Srv; @@ -222,7 +221,7 @@ mod tests { } fn call(&self, _: ()) -> Self::Future { - err(()) + ready(Err(())) } } @@ -243,7 +242,9 @@ mod tests { #[actix_rt::test] async fn test_new_service() { - let new_srv = (|| ok::<_, ()>(Srv)).into_factory().map_err(|_| "error"); + let new_srv = (|| ready(Ok::<_, ()>(Srv))) + .into_factory() + .map_err(|_| "error"); let srv = new_srv.new_service(&()).await.unwrap(); let res = srv.call(()).await; assert!(res.is_err()); diff --git a/actix-service/src/pipeline.rs b/actix-service/src/pipeline.rs index 2617d0ed..4286e997 100644 --- a/actix-service/src/pipeline.rs +++ b/actix-service/src/pipeline.rs @@ -6,12 +6,14 @@ use core::{ task::{Context, Poll}, }; -use crate::and_then::{AndThenService, AndThenServiceFactory}; -use crate::map::{Map, MapServiceFactory}; -use crate::map_err::{MapErr, MapErrServiceFactory}; -use crate::map_init_err::MapInitErr; -use crate::then::{ThenService, ThenServiceFactory}; -use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory}; +use crate::{ + and_then::{AndThenService, AndThenServiceFactory}, + map::{Map, MapServiceFactory}, + map_err::{MapErr, MapErrServiceFactory}, + map_init_err::MapInitErr, + then::{ThenService, ThenServiceFactory}, + IntoService, IntoServiceFactory, Service, ServiceFactory, +}; /// Construct new pipeline with one service in pipeline chain. pub(crate) fn pipeline(service: I) -> Pipeline @@ -50,7 +52,7 @@ where /// Call another service after call to this one has resolved successfully. /// /// This function can be used to chain two services together and ensure that - /// the second service isn't called until call to the fist service have + /// the second service isn't called until call to the first service have /// finished. Result of the call to the first service is used as an /// input parameter for the second service's call. /// @@ -252,10 +254,7 @@ where } /// Map this service's error to a different error, returning a new service. - pub fn map_err( - self, - f: F, - ) -> PipelineFactory, Req> + pub fn map_err(self, f: F) -> PipelineFactory, Req> where Self: Sized, F: Fn(SF::Error) -> E + Clone, diff --git a/actix-service/src/ready.rs b/actix-service/src/ready.rs deleted file mode 100644 index 8b0c2ea7..00000000 --- a/actix-service/src/ready.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! When MSRV is 1.48, replace with `core::future::Ready` and `core::future::ready()`. - -use core::{ - future::Future, - pin::Pin, - task::{Context, Poll}, -}; - -/// Future for the [`ready`](ready()) function. -#[derive(Debug, Clone)] -#[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct Ready { - val: Option, -} - -impl Ready { - /// Unwraps the value from this immediately ready future. - #[inline] - pub fn into_inner(mut self) -> T { - self.val.take().unwrap() - } -} - -impl Unpin for Ready {} - -impl Future for Ready { - type Output = T; - - #[inline] - fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { - let val = self.val.take().expect("Ready can not be polled twice."); - Poll::Ready(val) - } -} - -/// Creates a future that is immediately ready with a value. -#[allow(dead_code)] -pub(crate) fn ready(val: T) -> Ready { - Ready { val: Some(val) } -} - -/// Create a future that is immediately ready with a success value. -#[allow(dead_code)] -pub(crate) fn ok(val: T) -> Ready> { - Ready { val: Some(Ok(val)) } -} - -/// Create a future that is immediately ready with an error value. -#[allow(dead_code)] -pub(crate) fn err(err: E) -> Ready> { - Ready { - val: Some(Err(err)), - } -} diff --git a/actix-service/src/then.rs b/actix-service/src/then.rs index 82b9dc94..09c14a07 100644 --- a/actix-service/src/then.rs +++ b/actix-service/src/then.rs @@ -241,15 +241,15 @@ mod tests { use alloc::rc::Rc; use core::{ cell::Cell, + future::{ready, Ready}, task::{Context, Poll}, }; use futures_util::future::lazy; use crate::{ - err, ok, pipeline::{pipeline, pipeline_factory}, - ready, Ready, Service, ServiceFactory, + Service, ServiceFactory, }; #[derive(Clone)] @@ -267,8 +267,8 @@ mod tests { fn call(&self, req: Result<&'static str, &'static str>) -> Self::Future { match req { - Ok(msg) => ok(msg), - Err(_) => err(()), + Ok(msg) => ready(Ok(msg)), + Err(_) => ready(Err(())), } } } @@ -287,8 +287,8 @@ mod tests { fn call(&self, req: Result<&'static str, ()>) -> Self::Future { match req { - Ok(msg) => ok((msg, "ok")), - Err(()) => ok(("srv2", "err")), + Ok(msg) => ready(Ok((msg, "ok"))), + Err(()) => ready(Ok(("srv2", "err"))), } } } diff --git a/actix-service/src/transform.rs b/actix-service/src/transform.rs index e39f3058..98ad7a07 100644 --- a/actix-service/src/transform.rs +++ b/actix-service/src/transform.rs @@ -221,14 +221,15 @@ where #[cfg(test)] mod tests { - use core::time::Duration; - - use actix_utils::future::{ready, Ready}; + use core::{ + future::{ready, Ready}, + time::Duration, + }; use super::*; - use crate::Service; // pseudo-doctest for Transform trait + #[allow(unused)] pub struct TimeoutTransform { timeout: Duration, } @@ -250,6 +251,7 @@ mod tests { } // pseudo-doctest for Transform trait + #[allow(unused)] pub struct Timeout { service: S, _timeout: Duration, diff --git a/actix-tls/CHANGES.md b/actix-tls/CHANGES.md index da7bec65..6374e0ec 100644 --- a/actix-tls/CHANGES.md +++ b/actix-tls/CHANGES.md @@ -1,30 +1,76 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased +- Minimum supported Rust version (MSRV) is now 1.88. +- breaking: Use `core::future::{ready, Ready}` instead of actix-utils' + +## 3.5.0 + +- Update `rustls-native-certs` (`0.7`) dependency to `0.8`. +- Minimum supported Rust version (MSRV) is now 1.76. + +## 3.4.0 + +- Add `rustls-0_23`, `rustls-0_23-webpki-roots`, and `rustls-0_23-native-roots` crate features. +- Minimum supported Rust version (MSRV) is now 1.70. + +## 3.3.0 + +- Add `rustls-0_22` crate feature which excludes any root certificate methods or re-exports. + +## 3.2.0 + +- Support Rustls v0.22. +- Add `{accept, connect}::rustls_0_22` modules. +- Add `rustls-0_21-native-roots` and `rustls-0_20-native-roots` crate features which utilize the `rustls-native-certs` crate to enable a `native_roots_cert_store()` functions in each rustls-based `connect` module. +- Implement `Host` for `http::Uri` (`http` crate version `1`). + +## 3.1.1 + +- Fix `rustls` v0.21 version requirement. + +## 3.1.0 + +- Support Rustls v0.21. +- Add `{accept, connect}::rustls_0_21` modules. +- Add `{accept, connect}::rustls_0_20` alias for `{accept, connect}::rustls` modules. +- Minimum supported Rust version (MSRV) is now 1.65. + +## 3.0.4 + +- Logs emitted now use the `tracing` crate with `log` compatibility. [#451] + +[#451]: https://github.com/actix/actix-net/pull/451 + +## 3.0.3 + +- No significant changes since `3.0.2`. + +## 3.0.2 -## 3.0.2 - 2022-01-28 - Expose `connect::Connection::new`. [#439] [#439]: https://github.com/actix/actix-net/pull/439 +## 3.0.1 -## 3.0.1 - 2022-01-11 - No significant changes since `3.0.0`. +## 3.0.0 -## 3.0.0 - 2021-12-26 - No significant changes since `3.0.0-rc.2`. +## 3.0.0-rc.2 -## 3.0.0-rc.2 - 2021-12-10 - Re-export `openssl::SslConnectorBuilder` in `connect::openssl::reexports`. [#429] [#429]: https://github.com/actix/actix-net/pull/429 +## 3.0.0-rc.1 -## 3.0.0-rc.1 - 2021-11-29 ### Added + - Derive `Debug` for `connect::Connection`. [#422] - Implement `Display` for `accept::TlsError`. [#422] - Implement `Error` for `accept::TlsError` where both types also implement `Error`. [#422] @@ -34,6 +80,7 @@ - Implement `Default` for `connect::ConnectorService`. [#423] ### Changed + - The crate's default features flags no longer include `uri`. [#422] - Useful re-exports from underlying TLS crates are exposed in a `reexports` modules in all acceptors and connectors. - Convert `connect::ResolverService` from enum to struct. [#422] @@ -50,6 +97,7 @@ - Inline modules in `connect::tls` to `connect` module. [#422] ### Removed + - Remove `connect::{new_connector, new_connector_factory, default_connector, default_connector_factory}` methods. [#422] - Remove `connect::native_tls::Connector::service` method. [#422] - Remove redundant `connect::Connection::from_parts` method. [#422] @@ -57,8 +105,8 @@ [#422]: https://github.com/actix/actix-net/pull/422 [#423]: https://github.com/actix/actix-net/pull/423 +## 3.0.0-beta.9 -## 3.0.0-beta.9 - 2021-11-22 - Add configurable timeout for accepting TLS connection. [#393] - Added `TlsError::Timeout` variant. [#393] - All TLS acceptor services now use `TlsError` for their error types. [#393] @@ -67,31 +115,30 @@ [#393]: https://github.com/actix/actix-net/pull/393 [#420]: https://github.com/actix/actix-net/pull/420 +## 3.0.0-beta.8 -## 3.0.0-beta.8 - 2021-11-15 - Add `Connect::request` for getting a reference to the connection request. [#415] [#415]: https://github.com/actix/actix-net/pull/415 +## 3.0.0-beta.7 -## 3.0.0-beta.7 - 2021-10-20 - Add `webpki_roots_cert_store()` to get rustls compatible webpki roots cert store. [#401] - Alias `connect::ssl` to `connect::tls`. [#401] [#401]: https://github.com/actix/actix-net/pull/401 +## 3.0.0-beta.6 -## 3.0.0-beta.6 - 2021-10-19 - Update `tokio-rustls` to `0.23` which uses `rustls` `0.20`. [#396] - Removed a re-export of `Session` from `rustls` as it no longer exist. [#396] - Minimum supported Rust version (MSRV) is now 1.52. [#396]: https://github.com/actix/actix-net/pull/396 +## 3.0.0-beta.5 -## 3.0.0-beta.5 - 2021-03-29 -- Changed `connect::ssl::rustls::RustlsConnectorService` to return error when `DNSNameRef` - generation failed instead of panic. [#296] +- Changed `connect::ssl::rustls::RustlsConnectorService` to return error when `DNSNameRef` generation failed instead of panic. [#296] - Remove `connect::ssl::openssl::OpensslConnectServiceFactory`. [#297] - Remove `connect::ssl::openssl::OpensslConnectService`. [#297] - Add `connect::ssl::native_tls` module for native tls support. [#295] @@ -103,74 +150,72 @@ [#297]: https://github.com/actix/actix-net/pull/297 [#299]: https://github.com/actix/actix-net/pull/299 +## 3.0.0-beta.4 -## 3.0.0-beta.4 - 2021-02-24 - Rename `accept::openssl::{SslStream => TlsStream}`. - Add `connect::Connect::set_local_addr` to attach local `IpAddr`. [#282] - `connector::TcpConnector` service will try to bind to local_addr of `IpAddr` when given. [#282] [#282]: https://github.com/actix/actix-net/pull/282 +## 3.0.0-beta.3 -## 3.0.0-beta.3 - 2021-02-06 - Remove `trust-dns-proto` and `trust-dns-resolver`. [#248] - Use `std::net::ToSocketAddrs` as simple and basic default resolver. [#248] - Add `Resolve` trait for custom DNS resolvers. [#248] - Add `Resolver::new_custom` function to construct custom resolvers. [#248] -- Export `webpki_roots::TLS_SERVER_ROOTS` in `actix_tls::connect` mod and remove - the export from `actix_tls::accept` [#248] -- Remove `ConnectTakeAddrsIter`. `Connect::take_addrs` now returns `ConnectAddrsIter<'static>` - as owned iterator. [#248] +- Export `webpki_roots::TLS_SERVER_ROOTS` in `actix_tls::connect` mod and remove the export from `actix_tls::accept` [#248] +- Remove `ConnectTakeAddrsIter`. `Connect::take_addrs` now returns `ConnectAddrsIter<'static>` as owned iterator. [#248] - Rename `Address::{host => hostname}` to more accurately describe which URL segment is returned. - Update `actix-rt` to `2.0.0`. [#273] [#248]: https://github.com/actix/actix-net/pull/248 [#273]: https://github.com/actix/actix-net/pull/273 +## 3.0.0-beta.2 -## 3.0.0-beta.2 - 2021-xx-xx - Depend on stable trust-dns packages. [#204] [#204]: https://github.com/actix/actix-net/pull/204 +## 3.0.0-beta.1 -## 3.0.0-beta.1 - 2020-12-29 - Move acceptors under `accept` module. [#238] - Merge `actix-connect` crate under `connect` module. [#238] - Add feature flags to enable acceptors and/or connectors individually. [#238] [#238]: https://github.com/actix/actix-net/pull/238 +## 2.0.0 -## 2.0.0 - 2020-09-03 - `nativetls::NativeTlsAcceptor` is renamed to `nativetls::Acceptor`. - Where possible, "SSL" terminology is replaced with "TLS". - * `SslError` is renamed to `TlsError`. - * `TlsError::Ssl` enum variant is renamed to `TlsError::Tls`. - * `max_concurrent_ssl_connect` is renamed to `max_concurrent_tls_connect`. + - `SslError` is renamed to `TlsError`. + - `TlsError::Ssl` enum variant is renamed to `TlsError::Tls`. + - `max_concurrent_ssl_connect` is renamed to `max_concurrent_tls_connect`. +## 2.0.0-alpha.2 -## 2.0.0-alpha.2 - 2020-08-17 - Update `rustls` dependency to 0.18 - Update `tokio-rustls` dependency to 0.14 - Update `webpki-roots` dependency to 0.20 +## [2.0.0-alpha.1] -## [2.0.0-alpha.1] - 2020-03-03 - Update `rustls` dependency to 0.17 - Update `tokio-rustls` dependency to 0.13 - Update `webpki-roots` dependency to 0.19 +## [1.0.0] -## [1.0.0] - 2019-12-11 - 1.0.0 release +## [1.0.0-alpha.3] -## [1.0.0-alpha.3] - 2019-12-07 - Migrate to tokio 0.2 - Enable rustls acceptor service - Enable native-tls acceptor service +## [1.0.0-alpha.1] -## [1.0.0-alpha.1] - 2019-12-02 - Split openssl acceptor from actix-server package diff --git a/actix-tls/Cargo.toml b/actix-tls/Cargo.toml old mode 100755 new mode 100644 index 28c8e04a..00d5d44b --- a/actix-tls/Cargo.toml +++ b/actix-tls/Cargo.toml @@ -1,24 +1,27 @@ [package] name = "actix-tls" -version = "3.0.2" -authors = [ - "Nikolay Kim ", - "Rob Ede ", -] +version = "3.5.0" +authors = ["Nikolay Kim ", "Rob Ede "] description = "TLS acceptor and connector services for Actix ecosystem" keywords = ["network", "tls", "ssl", "async", "transport"] repository = "https://github.com/actix/actix-net.git" categories = ["network-programming", "asynchronous", "cryptography"] -license = "MIT OR Apache-2.0" -edition = "2018" +license.workspace = true +edition.workspace = true +rust-version.workspace = true [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] -[lib] -name = "actix_tls" -path = "src/lib.rs" +[package.metadata.cargo_check_external_types] +allowed_external_types = ["actix_service::*", "actix_utils::*", "futures_core::*", "tokio::*"] + +[package.metadata.cargo-machete] +ignored = [ + "rustls_021", # specified to force version with add_trust_anchors method + "rustls_webpki_0101", # specified to force secure version +] [features] default = ["accept", "connect"] @@ -30,54 +33,103 @@ accept = [] connect = [] # use openssl impls -openssl = ["tls-openssl", "tokio-openssl"] +openssl = ["dep:tls-openssl", "dep:tokio-openssl"] -# use rustls impls -rustls = ["tokio-rustls", "webpki-roots"] +# alias for backwards compat +rustls = ["rustls-0_20"] + +# use rustls v0.20 impls +rustls-0_20 = ["rustls-0_20-webpki-roots"] +rustls-0_20-webpki-roots = ["tokio-rustls-023", "webpki-roots-022"] +rustls-0_20-native-roots = ["tokio-rustls-023", "dep:rustls-native-certs-06"] + +# use rustls v0.21 impls +rustls-0_21 = ["rustls-0_21-webpki-roots"] +rustls-0_21-webpki-roots = ["tokio-rustls-024", "webpki-roots-025"] +rustls-0_21-native-roots = ["tokio-rustls-024", "dep:rustls-native-certs-06"] + +# use rustls v0.22 impls +rustls-0_22 = ["dep:tokio-rustls-025", "dep:rustls-pki-types-1"] +rustls-0_22-webpki-roots = ["rustls-0_22", "dep:webpki-roots-026"] +rustls-0_22-native-roots = ["rustls-0_22", "dep:rustls-native-certs-08"] + +# use rustls v0.23 impls +rustls-0_23 = ["dep:tokio-rustls-026", "dep:rustls-pki-types-1"] +rustls-0_23-webpki-roots = ["rustls-0_23", "dep:webpki-roots-026"] +rustls-0_23-native-roots = ["rustls-0_23", "dep:rustls-native-certs-08"] # use native-tls impls -native-tls = ["tokio-native-tls"] +native-tls = ["dep:tokio-native-tls"] # support http::Uri as connect address -uri = ["http"] +uri = ["dep:http-0_2", "dep:http-1"] [dependencies] -actix-codec = "0.4.2" -actix-rt = { version = "2.2.0", default-features = false } -actix-service = "2.0.0" -actix-utils = "3.0.0" - +actix-rt = { version = "2.2", default-features = false } +actix-service = "2" +actix-utils = "3" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } -log = "0.4" +impl-more = "0.1" pin-project-lite = "0.2.7" -tokio-util = { version = "0.6.3", default-features = false } +tokio = "1.44.2" +tokio-util = "0.7" +tracing = { version = "0.1.30", default-features = false, features = ["log"] } # uri -http = { version = "0.2.3", optional = true } +http-0_2 = { package = "http", version = "0.2.3", optional = true } +http-1 = { package = "http", version = "1", optional = true } # openssl -tls-openssl = { package = "openssl", version = "0.10.9", optional = true } +tls-openssl = { package = "openssl", version = "0.10.55", optional = true } tokio-openssl = { version = "0.6", optional = true } -# rustls -tokio-rustls = { version = "0.23", optional = true } -webpki-roots = { version = "0.22", optional = true } +# rustls PKI types +rustls-pki-types-1 = { package = "rustls-pki-types", version = "1", optional = true } + +# rustls v0.20 +tokio-rustls-023 = { package = "tokio-rustls", version = "0.23", optional = true } + +# rustls v0.21 +tokio-rustls-024 = { package = "tokio-rustls", version = "0.24", optional = true } + +# rustls v0.22 +tokio-rustls-025 = { package = "tokio-rustls", version = "0.25", optional = true } + +# rustls v0.23 +tokio-rustls-026 = { package = "tokio-rustls", version = "0.26", default-features = false, optional = true } + +# webpki-roots used with rustls features +webpki-roots-022 = { package = "webpki-roots", version = "0.22", optional = true } +webpki-roots-025 = { package = "webpki-roots", version = "0.25", optional = true } +webpki-roots-026 = { package = "webpki-roots", version = "0.26", optional = true } + +# native root certificates for rustls impls +rustls-native-certs-06 = { package = "rustls-native-certs", version = "0.6", optional = true } +rustls-native-certs-08 = { package = "rustls-native-certs", version = "0.8", optional = true } # native-tls tokio-native-tls = { version = "0.3", optional = true } +[target.'cfg(any())'.dependencies] +rustls-021 = { package = "rustls", version = "0.21.6", optional = true } # force version with add_trust_anchors method +rustls-webpki-0101 = { package = "rustls-webpki", version = "0.101.4", optional = true } # force secure version + [dev-dependencies] -actix-rt = "2.2.0" -actix-server = "2.0.0" +actix-codec = "0.5" +actix-rt = "2.2" +actix-server = "2" bytes = "1" -env_logger = "0.9" -futures-util = { version = "0.3.7", default-features = false, features = ["sink"] } -log = "0.4" -rcgen = "0.8" -rustls-pemfile = "0.2.1" -tokio-rustls = { version = "0.23", features = ["dangerous_configuration"] } -trust-dns-resolver = "0.20.0" +futures-util = { version = "0.3.17", default-features = false, features = ["sink"] } +hickory-resolver = "0.25" +itertools = "0.14" +pretty_env_logger = "0.5" +rcgen = "0.13" +rustls-pemfile = "2" +tokio-rustls-026 = { package = "tokio-rustls", version = "0.26" } [[example]] name = "accept-rustls" -required-features = ["accept", "rustls"] +required-features = ["accept", "rustls-0_23"] + +[lints] +workspace = true diff --git a/actix-tls/README.md b/actix-tls/README.md new file mode 100644 index 00000000..a6c15fc5 --- /dev/null +++ b/actix-tls/README.md @@ -0,0 +1,21 @@ +# `actix-tls` + +> TLS acceptor and connector services for the Actix ecosystem. + + + +[![crates.io](https://img.shields.io/crates/v/actix-tls?label=latest)](https://crates.io/crates/actix-tls) +[![Documentation](https://docs.rs/actix-tls/badge.svg?version=3.5.0)](https://docs.rs/actix-tls/3.5.0) +[![Version](https://img.shields.io/badge/rustc-1.88+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-tls.svg) +
+[![Dependency Status](https://deps.rs/crate/actix-tls/3.5.0/status.svg)](https://deps.rs/crate/actix-tls/3.5.0) +![Download](https://img.shields.io/crates/d/actix-tls.svg) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) + + + +## Resources + +- [Library Documentation](https://docs.rs/actix-tls) +- [Examples](/actix-tls/examples) diff --git a/actix-tls/examples/accept-rustls.rs b/actix-tls/examples/accept-rustls.rs index a0550495..7033ba1a 100644 --- a/actix-tls/examples/accept-rustls.rs +++ b/actix-tls/examples/accept-rustls.rs @@ -15,14 +15,12 @@ //! http --verify=false https://127.0.0.1:8443 //! ``` -// this use only exists because of how we have organised the crate -// it is not necessary for your actual code -use tokio_rustls::rustls; - +// this `use` is only exists because of how we have organised the crate +// it is not necessary for your actual code; you should import from `rustls` normally use std::{ - env, fs::File, io::{self, BufReader}, + path::PathBuf, sync::{ atomic::{AtomicUsize, Ordering}, Arc, @@ -32,32 +30,40 @@ use std::{ use actix_rt::net::TcpStream; use actix_server::Server; use actix_service::ServiceFactoryExt as _; -use actix_tls::accept::rustls::{Acceptor as RustlsAcceptor, TlsStream}; +use actix_tls::accept::rustls_0_23::{Acceptor as RustlsAcceptor, TlsStream}; use futures_util::future::ok; -use log::info; -use rustls::{server::ServerConfig, Certificate, PrivateKey}; +use itertools::Itertools as _; +use rustls::server::ServerConfig; use rustls_pemfile::{certs, rsa_private_keys}; +use rustls_pki_types_1::PrivateKeyDer; +use tokio_rustls_026::rustls; +use tracing::info; #[actix_rt::main] async fn main() -> io::Result<()> { - env::set_var("RUST_LOG", "info"); - env_logger::init(); + pretty_env_logger::formatted_timed_builder() + .parse_env(pretty_env_logger::env_logger::Env::default().default_filter_or("info")); + + let root_path = env!("CARGO_MANIFEST_DIR") + .parse::() + .unwrap() + .join("examples"); + let cert_path = root_path.clone().join("cert.pem"); + let key_path = root_path.clone().join("key.pem"); // Load TLS key and cert files - let cert_file = &mut BufReader::new(File::open("./examples/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("./examples/key.pem").unwrap()); + let cert_file = &mut BufReader::new(File::open(cert_path).unwrap()); + let key_file = &mut BufReader::new(File::open(key_path).unwrap()); - let cert_chain = certs(cert_file) - .unwrap() - .into_iter() - .map(Certificate) - .collect(); - let mut keys = rsa_private_keys(key_file).unwrap(); + let cert_chain = certs(cert_file); + let mut keys = rsa_private_keys(key_file); let tls_config = ServerConfig::builder() - .with_safe_defaults() .with_no_client_auth() - .with_single_cert(cert_chain, PrivateKey(keys.remove(0))) + .with_single_cert( + cert_chain.try_collect::<_, Vec<_>, _>()?, + PrivateKeyDer::Pkcs1(keys.next().unwrap()?), + ) .unwrap(); let tls_acceptor = RustlsAcceptor::new(tls_config); @@ -65,7 +71,7 @@ async fn main() -> io::Result<()> { let count = Arc::new(AtomicUsize::new(0)); let addr = ("127.0.0.1", 8443); - info!("starting server on port: {}", &addr.0); + info!("starting server at: {addr:?}"); Server::build() .bind("tls-example", addr, move || { @@ -74,7 +80,7 @@ async fn main() -> io::Result<()> { // Set up TLS service factory tls_acceptor .clone() - .map_err(|err| println!("Rustls error: {:?}", err)) + .map_err(|err| println!("Rustls error: {err:?}")) .and_then(move |stream: TlsStream| { let num = count.fetch_add(1, Ordering::Relaxed); info!("[{}] Got TLS connection: {:?}", num, &*stream); diff --git a/actix-tls/src/accept/mod.rs b/actix-tls/src/accept/mod.rs index 46710df8..c9d6cd73 100644 --- a/actix-tls/src/accept/mod.rs +++ b/actix-tls/src/accept/mod.rs @@ -10,20 +10,37 @@ use std::{ use actix_utils::counter::Counter; #[cfg(feature = "openssl")] -#[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub mod openssl; -#[cfg(feature = "rustls")] -#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] -pub mod rustls; +#[cfg(feature = "rustls-0_20")] +pub mod rustls_0_20; + +#[doc(hidden)] +#[cfg(feature = "rustls-0_20")] +pub use rustls_0_20 as rustls; + +#[cfg(feature = "rustls-0_21")] +pub mod rustls_0_21; + +#[cfg(feature = "rustls-0_22")] +pub mod rustls_0_22; + +#[cfg(feature = "rustls-0_23")] +pub mod rustls_0_23; #[cfg(feature = "native-tls")] -#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))] pub mod native_tls; pub(crate) static MAX_CONN: AtomicUsize = AtomicUsize::new(256); -#[cfg(any(feature = "openssl", feature = "rustls", feature = "native-tls"))] +#[cfg(any( + feature = "openssl", + feature = "rustls-0_20", + feature = "rustls-0_21", + feature = "rustls-0_22", + feature = "rustls-0_23", + feature = "native-tls", +))] pub(crate) const DEFAULT_TLS_HANDSHAKE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3); @@ -59,6 +76,25 @@ pub enum TlsError { Service(SvcErr), } +impl TlsError { + /// Casts the infallible service error type returned from acceptors into caller's type. + /// + /// # Examples + /// ``` + /// # use std::convert::Infallible; + /// # use actix_tls::accept::TlsError; + /// let a: TlsError = TlsError::Tls(42); + /// let _b: TlsError = a.into_service_error(); + /// ``` + pub fn into_service_error(self) -> TlsError { + match self { + Self::Timeout => TlsError::Timeout, + Self::Tls(err) => TlsError::Tls(err), + Self::Service(err) => match err {}, + } + } +} + impl fmt::Display for TlsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -83,25 +119,6 @@ where } } -impl TlsError { - /// Casts the infallible service error type returned from acceptors into caller's type. - /// - /// # Examples - /// ``` - /// # use std::convert::Infallible; - /// # use actix_tls::accept::TlsError; - /// let a: TlsError = TlsError::Tls(42); - /// let _b: TlsError = a.into_service_error(); - /// ``` - pub fn into_service_error(self) -> TlsError { - match self { - Self::Timeout => TlsError::Timeout, - Self::Tls(err) => TlsError::Tls(err), - Self::Service(err) => match err {}, - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/actix-tls/src/accept/native_tls.rs b/actix-tls/src/accept/native_tls.rs index b22c1ef2..ceb58fac 100644 --- a/actix-tls/src/accept/native_tls.rs +++ b/actix-tls/src/accept/native_tls.rs @@ -2,6 +2,7 @@ //! //! See [`Acceptor`] for main service factory docs. +use core::future::{ready, Ready as FutReady}; use std::{ convert::Infallible, io::{self, IoSlice}, @@ -10,21 +11,17 @@ use std::{ time::Duration, }; -use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use actix_rt::{ net::{ActixStream, Ready}, time::timeout, }; use actix_service::{Service, ServiceFactory}; -use actix_utils::{ - counter::Counter, - future::{ready, Ready as FutReady}, -}; +use actix_utils::counter::Counter; use futures_core::future::LocalBoxFuture; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio_native_tls::{native_tls::Error, TlsAcceptor}; use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER}; -use crate::impl_more; pub mod reexports { //! Re-exports from `native-tls` that are useful for acceptors. @@ -35,9 +32,8 @@ pub mod reexports { /// Wraps a `native-tls` based async TLS stream in order to implement [`ActixStream`]. pub struct TlsStream(tokio_native_tls::TlsStream); -impl_more::from! { tokio_native_tls::TlsStream => TlsStream } -impl_more::deref! { TlsStream => 0: tokio_native_tls::TlsStream } -impl_more::deref_mut! { TlsStream => 0 } +impl_more::impl_from!( in tokio_native_tls::TlsStream => TlsStream); +impl_more::impl_deref_and_mut!( in TlsStream => tokio_native_tls::TlsStream); impl AsyncRead for TlsStream { fn poll_read( @@ -75,17 +71,17 @@ impl AsyncWrite for TlsStream { } fn is_write_vectored(&self) -> bool { - (&**self).is_write_vectored() + (**self).is_write_vectored() } } impl ActixStream for TlsStream { fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll> { - IO::poll_read_ready((&**self).get_ref().get_ref().get_ref(), cx) + IO::poll_read_ready((**self).get_ref().get_ref().get_ref(), cx) } fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll> { - IO::poll_write_ready((&**self).get_ref().get_ref().get_ref(), cx) + IO::poll_write_ready((**self).get_ref().get_ref().get_ref(), cx) } } diff --git a/actix-tls/src/accept/openssl.rs b/actix-tls/src/accept/openssl.rs index 6e65e5fc..4f595a65 100644 --- a/actix-tls/src/accept/openssl.rs +++ b/actix-tls/src/accept/openssl.rs @@ -2,6 +2,7 @@ //! //! See [`Acceptor`] for main service factory docs. +use core::future::{ready, Ready as FutReady}; use std::{ convert::Infallible, future::Future, @@ -11,21 +12,17 @@ use std::{ time::Duration, }; -use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use actix_rt::{ net::{ActixStream, Ready}, time::{sleep, Sleep}, }; use actix_service::{Service, ServiceFactory}; -use actix_utils::{ - counter::{Counter, CounterGuard}, - future::{ready, Ready as FutReady}, -}; +use actix_utils::counter::{Counter, CounterGuard}; use openssl::ssl::{Error, Ssl, SslAcceptor}; use pin_project_lite::pin_project; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER}; -use crate::impl_more; pub mod reexports { //! Re-exports from `openssl` that are useful for acceptors. @@ -38,9 +35,8 @@ pub mod reexports { /// Wraps an `openssl` based async TLS stream in order to implement [`ActixStream`]. pub struct TlsStream(tokio_openssl::SslStream); -impl_more::from! { tokio_openssl::SslStream => TlsStream } -impl_more::deref! { TlsStream => 0: tokio_openssl::SslStream } -impl_more::deref_mut! { TlsStream => 0 } +impl_more::impl_from!( in tokio_openssl::SslStream => TlsStream); +impl_more::impl_deref_and_mut!( in TlsStream => tokio_openssl::SslStream); impl AsyncRead for TlsStream { fn poll_read( @@ -78,17 +74,17 @@ impl AsyncWrite for TlsStream { } fn is_write_vectored(&self) -> bool { - (&**self).is_write_vectored() + (**self).is_write_vectored() } } impl ActixStream for TlsStream { fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll> { - IO::poll_read_ready((&**self).get_ref(), cx) + IO::poll_read_ready((**self).get_ref(), cx) } fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll> { - IO::poll_write_ready((&**self).get_ref(), cx) + IO::poll_write_ready((**self).get_ref(), cx) } } diff --git a/actix-tls/src/accept/rustls.rs b/actix-tls/src/accept/rustls_0_20.rs similarity index 87% rename from actix-tls/src/accept/rustls.rs rename to actix-tls/src/accept/rustls_0_20.rs index 35fbd34a..b2d785c9 100644 --- a/actix-tls/src/accept/rustls.rs +++ b/actix-tls/src/accept/rustls_0_20.rs @@ -1,7 +1,8 @@ -//! `rustls` based TLS connection acceptor service. +//! `rustls` v0.20 based TLS connection acceptor service. //! //! See [`Acceptor`] for main service factory docs. +use core::future::{ready, Ready as FutReady}; use std::{ convert::Infallible, future::Future, @@ -12,35 +13,30 @@ use std::{ time::Duration, }; -use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use actix_rt::{ net::{ActixStream, Ready}, time::{sleep, Sleep}, }; use actix_service::{Service, ServiceFactory}; -use actix_utils::{ - counter::{Counter, CounterGuard}, - future::{ready, Ready as FutReady}, -}; +use actix_utils::counter::{Counter, CounterGuard}; use pin_project_lite::pin_project; -use tokio_rustls::rustls::ServerConfig; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio_rustls::{Accept, TlsAcceptor}; +use tokio_rustls_023 as tokio_rustls; use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER}; -use crate::impl_more; pub mod reexports { //! Re-exports from `rustls` that are useful for acceptors. - pub use tokio_rustls::rustls::ServerConfig; + pub use tokio_rustls_023::rustls::ServerConfig; } /// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`]. pub struct TlsStream(tokio_rustls::server::TlsStream); -impl_more::from! { tokio_rustls::server::TlsStream => TlsStream } -impl_more::deref! { TlsStream => 0: tokio_rustls::server::TlsStream } -impl_more::deref_mut! { TlsStream => 0 } +impl_more::impl_from!( in tokio_rustls::server::TlsStream => TlsStream); +impl_more::impl_deref_and_mut!( in TlsStream => tokio_rustls::server::TlsStream); impl AsyncRead for TlsStream { fn poll_read( @@ -78,29 +74,29 @@ impl AsyncWrite for TlsStream { } fn is_write_vectored(&self) -> bool { - (&**self).is_write_vectored() + (**self).is_write_vectored() } } impl ActixStream for TlsStream { fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll> { - IO::poll_read_ready((&**self).get_ref().0, cx) + IO::poll_read_ready((**self).get_ref().0, cx) } fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll> { - IO::poll_write_ready((&**self).get_ref().0, cx) + IO::poll_write_ready((**self).get_ref().0, cx) } } /// Accept TLS connections via the `rustls` crate. pub struct Acceptor { - config: Arc, + config: Arc, handshake_timeout: Duration, } impl Acceptor { /// Constructs `rustls` based acceptor service factory. - pub fn new(config: ServerConfig) -> Self { + pub fn new(config: reexports::ServerConfig) -> Self { Acceptor { config: Arc::new(config), handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT, diff --git a/actix-tls/src/accept/rustls_0_21.rs b/actix-tls/src/accept/rustls_0_21.rs new file mode 100644 index 00000000..0725fcdd --- /dev/null +++ b/actix-tls/src/accept/rustls_0_21.rs @@ -0,0 +1,196 @@ +//! `rustls` v0.21 based TLS connection acceptor service. +//! +//! See [`Acceptor`] for main service factory docs. + +use core::future::{ready, Ready as FutReady}; +use std::{ + convert::Infallible, + future::Future, + io::{self, IoSlice}, + pin::Pin, + sync::Arc, + task::{Context, Poll}, + time::Duration, +}; + +use actix_rt::{ + net::{ActixStream, Ready}, + time::{sleep, Sleep}, +}; +use actix_service::{Service, ServiceFactory}; +use actix_utils::counter::{Counter, CounterGuard}; +use pin_project_lite::pin_project; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio_rustls::{Accept, TlsAcceptor}; +use tokio_rustls_024 as tokio_rustls; + +use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER}; + +pub mod reexports { + //! Re-exports from `rustls` that are useful for acceptors. + + pub use tokio_rustls_024::rustls::ServerConfig; +} + +/// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`]. +pub struct TlsStream(tokio_rustls::server::TlsStream); + +impl_more::impl_from!( in tokio_rustls::server::TlsStream => TlsStream); +impl_more::impl_deref_and_mut!( in TlsStream => tokio_rustls::server::TlsStream); + +impl AsyncRead for TlsStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_read(cx, buf) + } +} + +impl AsyncWrite for TlsStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_write(cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_flush(cx) + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_shutdown(cx) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_write_vectored(cx, bufs) + } + + fn is_write_vectored(&self) -> bool { + (**self).is_write_vectored() + } +} + +impl ActixStream for TlsStream { + fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll> { + IO::poll_read_ready((**self).get_ref().0, cx) + } + + fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll> { + IO::poll_write_ready((**self).get_ref().0, cx) + } +} + +/// Accept TLS connections via the `rustls` crate. +pub struct Acceptor { + config: Arc, + handshake_timeout: Duration, +} + +impl Acceptor { + /// Constructs `rustls` based acceptor service factory. + pub fn new(config: reexports::ServerConfig) -> Self { + Acceptor { + config: Arc::new(config), + handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT, + } + } + + /// Limit the amount of time that the acceptor will wait for a TLS handshake to complete. + /// + /// Default timeout is 3 seconds. + pub fn set_handshake_timeout(&mut self, handshake_timeout: Duration) -> &mut Self { + self.handshake_timeout = handshake_timeout; + self + } +} + +impl Clone for Acceptor { + fn clone(&self) -> Self { + Self { + config: self.config.clone(), + handshake_timeout: self.handshake_timeout, + } + } +} + +impl ServiceFactory for Acceptor { + type Response = TlsStream; + type Error = TlsError; + type Config = (); + type Service = AcceptorService; + type InitError = (); + type Future = FutReady>; + + fn new_service(&self, _: ()) -> Self::Future { + let res = MAX_CONN_COUNTER.with(|conns| { + Ok(AcceptorService { + acceptor: self.config.clone().into(), + conns: conns.clone(), + handshake_timeout: self.handshake_timeout, + }) + }); + + ready(res) + } +} + +/// Rustls based acceptor service. +pub struct AcceptorService { + acceptor: TlsAcceptor, + conns: Counter, + handshake_timeout: Duration, +} + +impl Service for AcceptorService { + type Response = TlsStream; + type Error = TlsError; + type Future = AcceptFut; + + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + if self.conns.available(cx) { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } + + fn call(&self, req: IO) -> Self::Future { + AcceptFut { + fut: self.acceptor.accept(req), + timeout: sleep(self.handshake_timeout), + _guard: self.conns.get(), + } + } +} + +pin_project! { + /// Accept future for Rustls service. + #[doc(hidden)] + pub struct AcceptFut { + fut: Accept, + #[pin] + timeout: Sleep, + _guard: CounterGuard, + } +} + +impl Future for AcceptFut { + type Output = Result, TlsError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + match Pin::new(&mut this.fut).poll(cx) { + Poll::Ready(Ok(stream)) => Poll::Ready(Ok(TlsStream(stream))), + Poll::Ready(Err(err)) => Poll::Ready(Err(TlsError::Tls(err))), + Poll::Pending => this.timeout.poll(cx).map(|_| Err(TlsError::Timeout)), + } + } +} diff --git a/actix-tls/src/accept/rustls_0_22.rs b/actix-tls/src/accept/rustls_0_22.rs new file mode 100644 index 00000000..fd23ee68 --- /dev/null +++ b/actix-tls/src/accept/rustls_0_22.rs @@ -0,0 +1,196 @@ +//! `rustls` v0.22 based TLS connection acceptor service. +//! +//! See [`Acceptor`] for main service factory docs. + +use core::future::{ready, Ready as FutReady}; +use std::{ + convert::Infallible, + future::Future, + io::{self, IoSlice}, + pin::Pin, + sync::Arc, + task::{Context, Poll}, + time::Duration, +}; + +use actix_rt::{ + net::{ActixStream, Ready}, + time::{sleep, Sleep}, +}; +use actix_service::{Service, ServiceFactory}; +use actix_utils::counter::{Counter, CounterGuard}; +use pin_project_lite::pin_project; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio_rustls::{Accept, TlsAcceptor}; +use tokio_rustls_025 as tokio_rustls; + +use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER}; + +pub mod reexports { + //! Re-exports from `rustls` that are useful for acceptors. + + pub use tokio_rustls_025::rustls::ServerConfig; +} + +/// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`]. +pub struct TlsStream(tokio_rustls::server::TlsStream); + +impl_more::impl_from!( in tokio_rustls::server::TlsStream => TlsStream); +impl_more::impl_deref_and_mut!( in TlsStream => tokio_rustls::server::TlsStream); + +impl AsyncRead for TlsStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_read(cx, buf) + } +} + +impl AsyncWrite for TlsStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_write(cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_flush(cx) + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_shutdown(cx) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_write_vectored(cx, bufs) + } + + fn is_write_vectored(&self) -> bool { + (**self).is_write_vectored() + } +} + +impl ActixStream for TlsStream { + fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll> { + IO::poll_read_ready((**self).get_ref().0, cx) + } + + fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll> { + IO::poll_write_ready((**self).get_ref().0, cx) + } +} + +/// Accept TLS connections via the `rustls` crate. +pub struct Acceptor { + config: Arc, + handshake_timeout: Duration, +} + +impl Acceptor { + /// Constructs `rustls` based acceptor service factory. + pub fn new(config: reexports::ServerConfig) -> Self { + Acceptor { + config: Arc::new(config), + handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT, + } + } + + /// Limit the amount of time that the acceptor will wait for a TLS handshake to complete. + /// + /// Default timeout is 3 seconds. + pub fn set_handshake_timeout(&mut self, handshake_timeout: Duration) -> &mut Self { + self.handshake_timeout = handshake_timeout; + self + } +} + +impl Clone for Acceptor { + fn clone(&self) -> Self { + Self { + config: self.config.clone(), + handshake_timeout: self.handshake_timeout, + } + } +} + +impl ServiceFactory for Acceptor { + type Response = TlsStream; + type Error = TlsError; + type Config = (); + type Service = AcceptorService; + type InitError = (); + type Future = FutReady>; + + fn new_service(&self, _: ()) -> Self::Future { + let res = MAX_CONN_COUNTER.with(|conns| { + Ok(AcceptorService { + acceptor: self.config.clone().into(), + conns: conns.clone(), + handshake_timeout: self.handshake_timeout, + }) + }); + + ready(res) + } +} + +/// Rustls based acceptor service. +pub struct AcceptorService { + acceptor: TlsAcceptor, + conns: Counter, + handshake_timeout: Duration, +} + +impl Service for AcceptorService { + type Response = TlsStream; + type Error = TlsError; + type Future = AcceptFut; + + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + if self.conns.available(cx) { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } + + fn call(&self, req: IO) -> Self::Future { + AcceptFut { + fut: self.acceptor.accept(req), + timeout: sleep(self.handshake_timeout), + _guard: self.conns.get(), + } + } +} + +pin_project! { + /// Accept future for Rustls service. + #[doc(hidden)] + pub struct AcceptFut { + fut: Accept, + #[pin] + timeout: Sleep, + _guard: CounterGuard, + } +} + +impl Future for AcceptFut { + type Output = Result, TlsError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + match Pin::new(&mut this.fut).poll(cx) { + Poll::Ready(Ok(stream)) => Poll::Ready(Ok(TlsStream(stream))), + Poll::Ready(Err(err)) => Poll::Ready(Err(TlsError::Tls(err))), + Poll::Pending => this.timeout.poll(cx).map(|_| Err(TlsError::Timeout)), + } + } +} diff --git a/actix-tls/src/accept/rustls_0_23.rs b/actix-tls/src/accept/rustls_0_23.rs new file mode 100644 index 00000000..26b87f86 --- /dev/null +++ b/actix-tls/src/accept/rustls_0_23.rs @@ -0,0 +1,196 @@ +//! `rustls` v0.23 based TLS connection acceptor service. +//! +//! See [`Acceptor`] for main service factory docs. + +use core::future::{ready, Ready as FutReady}; +use std::{ + convert::Infallible, + future::Future, + io::{self, IoSlice}, + pin::Pin, + sync::Arc, + task::{Context, Poll}, + time::Duration, +}; + +use actix_rt::{ + net::{ActixStream, Ready}, + time::{sleep, Sleep}, +}; +use actix_service::{Service, ServiceFactory}; +use actix_utils::counter::{Counter, CounterGuard}; +use pin_project_lite::pin_project; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio_rustls::{Accept, TlsAcceptor}; +use tokio_rustls_026 as tokio_rustls; + +use super::{TlsError, DEFAULT_TLS_HANDSHAKE_TIMEOUT, MAX_CONN_COUNTER}; + +pub mod reexports { + //! Re-exports from `rustls` that are useful for acceptors. + + pub use tokio_rustls_026::rustls::ServerConfig; +} + +/// Wraps a `rustls` based async TLS stream in order to implement [`ActixStream`]. +pub struct TlsStream(tokio_rustls::server::TlsStream); + +impl_more::impl_from!( in tokio_rustls::server::TlsStream => TlsStream); +impl_more::impl_deref_and_mut!( in TlsStream => tokio_rustls::server::TlsStream); + +impl AsyncRead for TlsStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_read(cx, buf) + } +} + +impl AsyncWrite for TlsStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_write(cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_flush(cx) + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_shutdown(cx) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + Pin::new(&mut **self.get_mut()).poll_write_vectored(cx, bufs) + } + + fn is_write_vectored(&self) -> bool { + (**self).is_write_vectored() + } +} + +impl ActixStream for TlsStream { + fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll> { + IO::poll_read_ready((**self).get_ref().0, cx) + } + + fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll> { + IO::poll_write_ready((**self).get_ref().0, cx) + } +} + +/// Accept TLS connections via the `rustls` crate. +pub struct Acceptor { + config: Arc, + handshake_timeout: Duration, +} + +impl Acceptor { + /// Constructs `rustls` based acceptor service factory. + pub fn new(config: reexports::ServerConfig) -> Self { + Acceptor { + config: Arc::new(config), + handshake_timeout: DEFAULT_TLS_HANDSHAKE_TIMEOUT, + } + } + + /// Limit the amount of time that the acceptor will wait for a TLS handshake to complete. + /// + /// Default timeout is 3 seconds. + pub fn set_handshake_timeout(&mut self, handshake_timeout: Duration) -> &mut Self { + self.handshake_timeout = handshake_timeout; + self + } +} + +impl Clone for Acceptor { + fn clone(&self) -> Self { + Self { + config: self.config.clone(), + handshake_timeout: self.handshake_timeout, + } + } +} + +impl ServiceFactory for Acceptor { + type Response = TlsStream; + type Error = TlsError; + type Config = (); + type Service = AcceptorService; + type InitError = (); + type Future = FutReady>; + + fn new_service(&self, _: ()) -> Self::Future { + let res = MAX_CONN_COUNTER.with(|conns| { + Ok(AcceptorService { + acceptor: self.config.clone().into(), + conns: conns.clone(), + handshake_timeout: self.handshake_timeout, + }) + }); + + ready(res) + } +} + +/// Rustls based acceptor service. +pub struct AcceptorService { + acceptor: TlsAcceptor, + conns: Counter, + handshake_timeout: Duration, +} + +impl Service for AcceptorService { + type Response = TlsStream; + type Error = TlsError; + type Future = AcceptFut; + + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + if self.conns.available(cx) { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } + + fn call(&self, req: IO) -> Self::Future { + AcceptFut { + fut: self.acceptor.accept(req), + timeout: sleep(self.handshake_timeout), + _guard: self.conns.get(), + } + } +} + +pin_project! { + /// Accept future for Rustls service. + #[doc(hidden)] + pub struct AcceptFut { + fut: Accept, + #[pin] + timeout: Sleep, + _guard: CounterGuard, + } +} + +impl Future for AcceptFut { + type Output = Result, TlsError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + match Pin::new(&mut this.fut).poll(cx) { + Poll::Ready(Ok(stream)) => Poll::Ready(Ok(TlsStream(stream))), + Poll::Ready(Err(err)) => Poll::Ready(Err(TlsError::Tls(err))), + Poll::Pending => this.timeout.poll(cx).map(|_| Err(TlsError::Timeout)), + } + } +} diff --git a/actix-tls/src/connect/connection.rs b/actix-tls/src/connect/connection.rs index 1abd4eff..3433ac1a 100644 --- a/actix-tls/src/connect/connection.rs +++ b/actix-tls/src/connect/connection.rs @@ -1,5 +1,4 @@ use super::Host; -use crate::impl_more; /// Wraps underlying I/O and the connection request that initiated it. #[derive(Debug)] @@ -8,8 +7,7 @@ pub struct Connection { pub(crate) io: IO, } -impl_more::deref! { Connection => io: IO } -impl_more::deref_mut! { Connection => io } +impl_more::impl_deref_and_mut!( in Connection => io: IO); impl Connection { /// Construct new `Connection` from request and IO parts. diff --git a/actix-tls/src/connect/connector.rs b/actix-tls/src/connect/connector.rs index a4276548..1009cbec 100644 --- a/actix-tls/src/connect/connector.rs +++ b/actix-tls/src/connect/connector.rs @@ -1,3 +1,4 @@ +use core::future::{ready, Ready}; use std::{ future::Future, pin::Pin, @@ -6,7 +7,6 @@ use std::{ use actix_rt::net::TcpStream; use actix_service::{Service, ServiceFactory}; -use actix_utils::future::{ok, Ready}; use futures_core::ready; use super::{ @@ -49,7 +49,7 @@ impl ServiceFactory> for Connector { type Future = Ready>; fn new_service(&self, _: ()) -> Self::Future { - ok(self.service()) + ready(Ok(self.service())) } } diff --git a/actix-tls/src/connect/host.rs b/actix-tls/src/connect/host.rs index 13780751..c4ff9a01 100644 --- a/actix-tls/src/connect/host.rs +++ b/actix-tls/src/connect/host.rs @@ -27,25 +27,25 @@ pub trait Host: Unpin + 'static { impl Host for String { fn hostname(&self) -> &str { - str_split_once(self, ':') + self.split_once(':') .map(|(hostname, _)| hostname) .unwrap_or(self) } fn port(&self) -> Option { - str_split_once(self, ':').and_then(|(_, port)| port.parse().ok()) + self.split_once(':').and_then(|(_, port)| port.parse().ok()) } } impl Host for &'static str { fn hostname(&self) -> &str { - str_split_once(self, ':') + self.split_once(':') .map(|(hostname, _)| hostname) .unwrap_or(self) } fn port(&self) -> Option { - str_split_once(self, ':').and_then(|(_, port)| port.parse().ok()) + self.split_once(':').and_then(|(_, port)| port.parse().ok()) } } @@ -69,11 +69,3 @@ mod tests { assert_connection_info_eq!("example.com:false:false", "example.com", None); } } - -// `str::split_once` is stabilized in 1.52.0 -fn str_split_once(str: &str, delimiter: char) -> Option<(&str, &str)> { - let mut splitn = str.splitn(2, delimiter); - let prefix = splitn.next()?; - let suffix = splitn.next()?; - Some((prefix, suffix)) -} diff --git a/actix-tls/src/connect/info.rs b/actix-tls/src/connect/info.rs index 7bd1e5f3..f34cfb6e 100644 --- a/actix-tls/src/connect/info.rs +++ b/actix-tls/src/connect/info.rs @@ -118,6 +118,7 @@ impl ConnectInfo { /// let mut addrs = conn.addrs(); /// assert_eq!(addrs.next().unwrap(), addr); /// ``` + #[allow(clippy::implied_bounds_in_impls)] pub fn addrs( &self, ) -> impl Iterator @@ -149,6 +150,7 @@ impl ConnectInfo { /// let mut addrs = conn.take_addrs(); /// assert_eq!(addrs.next().unwrap(), addr); /// ``` + #[allow(clippy::implied_bounds_in_impls)] pub fn take_addrs( &mut self, ) -> impl Iterator diff --git a/actix-tls/src/connect/mod.rs b/actix-tls/src/connect/mod.rs index 3511dd58..04337daa 100644 --- a/actix-tls/src/connect/mod.rs +++ b/actix-tls/src/connect/mod.rs @@ -22,25 +22,45 @@ mod resolver; pub mod tcp; #[cfg(feature = "uri")] -#[cfg_attr(docsrs, doc(cfg(feature = "uri")))] mod uri; #[cfg(feature = "openssl")] -#[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub mod openssl; -#[cfg(feature = "rustls")] -#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] -pub mod rustls; +#[cfg(any( + feature = "rustls-0_20-webpki-roots", + feature = "rustls-0_20-native-roots", +))] +pub mod rustls_0_20; + +#[doc(hidden)] +#[cfg(any( + feature = "rustls-0_20-webpki-roots", + feature = "rustls-0_20-native-roots", +))] +pub use rustls_0_20 as rustls; + +#[cfg(any( + feature = "rustls-0_21-webpki-roots", + feature = "rustls-0_21-native-roots", +))] +pub mod rustls_0_21; + +#[cfg(feature = "rustls-0_22")] +pub mod rustls_0_22; + +#[cfg(feature = "rustls-0_23")] +pub mod rustls_0_23; #[cfg(feature = "native-tls")] -#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))] pub mod native_tls; -pub use self::connection::Connection; -pub use self::connector::{Connector, ConnectorService}; -pub use self::error::ConnectError; -pub use self::host::Host; -pub use self::info::ConnectInfo; -pub use self::resolve::Resolve; -pub use self::resolver::{Resolver, ResolverService}; +pub use self::{ + connection::Connection, + connector::{Connector, ConnectorService}, + error::ConnectError, + host::Host, + info::ConnectInfo, + resolve::Resolve, + resolver::{Resolver, ResolverService}, +}; diff --git a/actix-tls/src/connect/native_tls.rs b/actix-tls/src/connect/native_tls.rs index 49222228..dbacd574 100644 --- a/actix-tls/src/connect/native_tls.rs +++ b/actix-tls/src/connect/native_tls.rs @@ -2,26 +2,24 @@ //! //! See [`TlsConnector`] for main connector service factory docs. +use core::future::{ready, Ready}; use std::io; use actix_rt::net::ActixStream; use actix_service::{Service, ServiceFactory}; -use actix_utils::future::{ok, Ready}; use futures_core::future::LocalBoxFuture; -use log::trace; use tokio_native_tls::{ native_tls::TlsConnector as NativeTlsConnector, TlsConnector as AsyncNativeTlsConnector, TlsStream as AsyncTlsStream, }; +use tracing::trace; use crate::connect::{Connection, Host}; pub mod reexports { //! Re-exports from `native-tls` and `tokio-native-tls` that are useful for connectors. - pub use tokio_native_tls::native_tls::TlsConnector; - - pub use tokio_native_tls::TlsStream as AsyncTlsStream; + pub use tokio_native_tls::{native_tls::TlsConnector, TlsStream as AsyncTlsStream}; } /// Connector service and factory using `native-tls`. @@ -53,7 +51,7 @@ where type Future = Ready>; fn new_service(&self, _: ()) -> Self::Future { - ok(self.clone()) + ready(Ok(self.clone())) } } @@ -75,17 +73,17 @@ where let connector = self.connector.clone(); Box::pin(async move { - trace!("SSL Handshake start for: {:?}", stream.hostname()); + trace!("TLS handshake start for: {:?}", stream.hostname()); connector .connect(stream.hostname(), io) .await .map(|res| { - trace!("SSL Handshake success: {:?}", stream.hostname()); + trace!("TLS handshake success: {:?}", stream.hostname()); stream.replace_io(res).1 }) - .map_err(|e| { - trace!("SSL Handshake error: {:?}", e); - io::Error::new(io::ErrorKind::Other, format!("{}", e)) + .map_err(|err| { + trace!("TLS handshake error: {err:?}"); + io::Error::other(format!("{err}")) }) }) } diff --git a/actix-tls/src/connect/openssl.rs b/actix-tls/src/connect/openssl.rs index 5739f6bc..3533f98b 100644 --- a/actix-tls/src/connect/openssl.rs +++ b/actix-tls/src/connect/openssl.rs @@ -2,6 +2,7 @@ //! //! See [`TlsConnector`] for main connector service factory docs. +use core::future::{ready, Ready}; use std::{ future::Future, io, @@ -11,21 +12,17 @@ use std::{ use actix_rt::net::ActixStream; use actix_service::{Service, ServiceFactory}; -use actix_utils::future::{ok, Ready}; use futures_core::ready; -use log::trace; use openssl::ssl::SslConnector; use tokio_openssl::SslStream as AsyncSslStream; +use tracing::trace; use crate::connect::{Connection, Host}; pub mod reexports { //! Re-exports from `openssl` and `tokio-openssl` that are useful for connectors. - pub use openssl::ssl::{ - Error, HandshakeError, SslConnector, SslConnectorBuilder, SslMethod, - }; - + pub use openssl::ssl::{Error, HandshakeError, SslConnector, SslConnectorBuilder, SslMethod}; pub use tokio_openssl::SslStream as AsyncSslStream; } @@ -67,9 +64,9 @@ where type Future = Ready>; fn new_service(&self, _: ()) -> Self::Future { - ok(TlsConnectorService { + ready(Ok(TlsConnectorService { connector: self.connector.clone(), - }) + })) } } @@ -98,7 +95,8 @@ where actix_service::always_ready!(); fn call(&self, stream: Connection) -> Self::Future { - trace!("SSL Handshake start for: {:?}", stream.hostname()); + trace!("TLS handshake start for: {:?}", stream.hostname()); + let (io, stream) = stream.replace_io(()); let host = stream.hostname(); @@ -138,15 +136,12 @@ where match ready!(Pin::new(this.io.as_mut().unwrap()).poll_connect(cx)) { Ok(_) => { let stream = this.stream.take().unwrap(); - trace!("SSL Handshake success: {:?}", stream.hostname()); + trace!("TLS handshake success: {:?}", stream.hostname()); Poll::Ready(Ok(stream.replace_io(this.io.take().unwrap()).1)) } Err(err) => { - trace!("SSL Handshake error: {:?}", err); - Poll::Ready(Err(io::Error::new( - io::ErrorKind::Other, - format!("{}", err), - ))) + trace!("TLS handshake error: {:?}", err); + Poll::Ready(Err(io::Error::other(format!("{err}")))) } } } diff --git a/actix-tls/src/connect/resolve.rs b/actix-tls/src/connect/resolve.rs index 33e2c676..9270ba35 100644 --- a/actix-tls/src/connect/resolve.rs +++ b/actix-tls/src/connect/resolve.rs @@ -13,11 +13,11 @@ use futures_core::future::LocalBoxFuture; /// use actix_tls::connect::{Resolve, Resolver}; /// use futures_util::future::LocalBoxFuture; /// -/// // use trust-dns async tokio resolver -/// use trust_dns_resolver::TokioAsyncResolver; +/// // use Hickory DNS tokio resolver +/// use hickory_resolver::TokioResolver; /// /// struct MyResolver { -/// trust_dns: TokioAsyncResolver, +/// hickory_dns: TokioResolver, /// }; /// /// // impl Resolve trait and convert given host address str and port to SocketAddr. @@ -29,7 +29,7 @@ use futures_core::future::LocalBoxFuture; /// ) -> LocalBoxFuture<'a, Result, Box>> { /// Box::pin(async move { /// let res = self -/// .trust_dns +/// .hickory_dns /// .lookup_ip(host) /// .await? /// .iter() @@ -41,7 +41,7 @@ use futures_core::future::LocalBoxFuture; /// } /// /// let my_resolver = MyResolver { -/// trust_dns: TokioAsyncResolver::tokio_from_system_conf().unwrap(), +/// hickory_dns: TokioResolver::builder_tokio().unwrap().build(), /// }; /// /// // wrap custom resolver diff --git a/actix-tls/src/connect/resolver.rs b/actix-tls/src/connect/resolver.rs index 245f919c..e5d1b891 100644 --- a/actix-tls/src/connect/resolver.rs +++ b/actix-tls/src/connect/resolver.rs @@ -1,3 +1,4 @@ +use core::future::{ready, Ready}; use std::{ future::Future, io, @@ -10,9 +11,8 @@ use std::{ use actix_rt::task::{spawn_blocking, JoinHandle}; use actix_service::{Service, ServiceFactory}; -use actix_utils::future::{ok, Ready}; use futures_core::{future::LocalBoxFuture, ready}; -use log::trace; +use tracing::trace; use super::{ConnectError, ConnectInfo, Host, Resolve}; @@ -45,7 +45,7 @@ impl ServiceFactory> for Resolver { type Future = Ready>; fn new_service(&self, _: ()) -> Self::Future { - ok(self.resolver.clone()) + ready(Ok(self.resolver.clone())) } } diff --git a/actix-tls/src/connect/rustls.rs b/actix-tls/src/connect/rustls_0_20.rs similarity index 59% rename from actix-tls/src/connect/rustls.rs rename to actix-tls/src/connect/rustls_0_20.rs index 641ddd23..9e19b754 100644 --- a/actix-tls/src/connect/rustls.rs +++ b/actix-tls/src/connect/rustls_0_20.rs @@ -2,8 +2,8 @@ //! //! See [`TlsConnector`] for main connector service factory docs. +use core::future::{ready, Ready}; use std::{ - convert::TryFrom, future::Future, io, pin::Pin, @@ -13,31 +13,51 @@ use std::{ use actix_rt::net::ActixStream; use actix_service::{Service, ServiceFactory}; -use actix_utils::future::{ok, Ready}; use futures_core::ready; -use log::trace; -use tokio_rustls::rustls::{client::ServerName, OwnedTrustAnchor, RootCertStore}; -use tokio_rustls::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig}; -use tokio_rustls::{Connect as RustlsConnect, TlsConnector as RustlsTlsConnector}; -use webpki_roots::TLS_SERVER_ROOTS; +use tokio_rustls::{ + client::TlsStream as AsyncTlsStream, + rustls::{client::ServerName, ClientConfig, RootCertStore}, + Connect as RustlsConnect, TlsConnector as RustlsTlsConnector, +}; +use tokio_rustls_023 as tokio_rustls; use crate::connect::{Connection, Host}; pub mod reexports { - //! Re-exports from `rustls` and `webpki_roots` that are useful for connectors. + //! Re-exports from the `rustls` v0.20 ecosystem that are useful for connectors. - pub use tokio_rustls::rustls::ClientConfig; + pub use tokio_rustls_023::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig}; + #[cfg(feature = "rustls-0_20-webpki-roots")] + pub use webpki_roots_022::TLS_SERVER_ROOTS; +} - pub use tokio_rustls::client::TlsStream as AsyncTlsStream; +/// Returns root certificates via `rustls-native-certs` crate as a rustls certificate store. +/// +/// See [`rustls_native_certs::load_native_certs()`] for more info on behavior and errors. +/// +/// [`rustls_native_certs::load_native_certs()`]: rustls_native_certs_06::load_native_certs() +#[cfg(feature = "rustls-0_20-native-roots")] +pub fn native_roots_cert_store() -> io::Result { + let mut root_certs = RootCertStore::empty(); - pub use webpki_roots::TLS_SERVER_ROOTS; + for cert in rustls_native_certs_06::load_native_certs()? { + root_certs + .add(&tokio_rustls_023::rustls::Certificate(cert.0)) + .unwrap(); + } + + Ok(root_certs) } /// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store. +#[cfg(feature = "rustls-0_20-webpki-roots")] pub fn webpki_roots_cert_store() -> RootCertStore { + use tokio_rustls_023::rustls; + let mut root_certs = RootCertStore::empty(); - for cert in TLS_SERVER_ROOTS.0 { - let cert = OwnedTrustAnchor::from_subject_spki_name_constraints( + + for cert in webpki_roots_022::TLS_SERVER_ROOTS.0 { + let cert = rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( cert.subject, cert.spki, cert.name_constraints, @@ -45,6 +65,7 @@ pub fn webpki_roots_cert_store() -> RootCertStore { let certs = vec![cert].into_iter(); root_certs.add_server_trust_anchors(certs); } + root_certs } @@ -79,9 +100,9 @@ where type Future = Ready>; fn new_service(&self, _: ()) -> Self::Future { - ok(TlsConnectorService { + ready(Ok(TlsConnectorService { connector: self.connector.clone(), - }) + })) } } @@ -103,12 +124,13 @@ where actix_service::always_ready!(); fn call(&self, connection: Connection) -> Self::Future { - trace!("SSL Handshake start for: {:?}", connection.hostname()); + tracing::trace!("TLS handshake start for: {:?}", connection.hostname()); let (stream, connection) = connection.replace_io(()); match ServerName::try_from(connection.hostname()) { Ok(host) => ConnectFut::Future { - connect: RustlsTlsConnector::from(self.connector.clone()).connect(host, stream), + connect: RustlsTlsConnector::from(Arc::clone(&self.connector)) + .connect(host, stream), connection: Some(connection), }, Err(_) => ConnectFut::InvalidDns, @@ -118,6 +140,7 @@ where /// Connect future for Rustls service. #[doc(hidden)] +#[allow(clippy::large_enum_variant)] pub enum ConnectFut { /// See issue InvalidDns, @@ -132,17 +155,22 @@ where R: Host, IO: ActixStream, { - type Output = Result>, io::Error>; + type Output = io::Result>>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.get_mut() { - Self::InvalidDns => Poll::Ready(Err( - io::Error::new(io::ErrorKind::Other, "rustls currently only handles hostname-based connections. See https://github.com/briansmith/webpki/issues/54") - )), - Self::Future { connect, connection } => { + Self::InvalidDns => Poll::Ready(Err(io::Error::other( + "Rustls v0.20 can only handle hostname-based connections. Enable the `rustls-0_21` \ + feature and use the Rustls v0.21 utilities to gain this feature.", + ))), + + Self::Future { + connect, + connection, + } => { let stream = ready!(Pin::new(connect).poll(cx))?; let connection = connection.take().unwrap(); - trace!("SSL Handshake success: {:?}", connection.hostname()); + tracing::trace!("TLS handshake success: {:?}", connection.hostname()); Poll::Ready(Ok(connection.replace_io(stream).1)) } } diff --git a/actix-tls/src/connect/rustls_0_21.rs b/actix-tls/src/connect/rustls_0_21.rs new file mode 100644 index 00000000..58cbfc68 --- /dev/null +++ b/actix-tls/src/connect/rustls_0_21.rs @@ -0,0 +1,177 @@ +//! Rustls based connector service. +//! +//! See [`TlsConnector`] for main connector service factory docs. + +use core::future::{ready, Ready}; +use std::{ + future::Future, + io, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; + +use actix_rt::net::ActixStream; +use actix_service::{Service, ServiceFactory}; +use futures_core::ready; +use tokio_rustls::{ + client::TlsStream as AsyncTlsStream, + rustls::{client::ServerName, ClientConfig, RootCertStore}, + Connect as RustlsConnect, TlsConnector as RustlsTlsConnector, +}; +use tokio_rustls_024 as tokio_rustls; + +use crate::connect::{Connection, Host}; + +pub mod reexports { + //! Re-exports from the `rustls` v0.21 ecosystem that are useful for connectors. + + pub use tokio_rustls_024::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig}; + #[cfg(feature = "rustls-0_21-webpki-roots")] + pub use webpki_roots_025::TLS_SERVER_ROOTS; +} + +/// Returns root certificates via `rustls-native-certs` crate as a rustls certificate store. +/// +/// See [`rustls_native_certs::load_native_certs()`] for more info on behavior and errors. +/// +/// [`rustls_native_certs::load_native_certs()`]: rustls_native_certs_06::load_native_certs() +#[cfg(feature = "rustls-0_21-native-roots")] +pub fn native_roots_cert_store() -> io::Result { + let mut root_certs = RootCertStore::empty(); + + for cert in rustls_native_certs_06::load_native_certs()? { + root_certs + .add(&tokio_rustls_024::rustls::Certificate(cert.0)) + .unwrap(); + } + + Ok(root_certs) +} + +/// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store. +#[cfg(feature = "rustls-0_21-webpki-roots")] +pub fn webpki_roots_cert_store() -> RootCertStore { + use tokio_rustls_024::rustls; + + let mut root_certs = RootCertStore::empty(); + + for cert in webpki_roots_025::TLS_SERVER_ROOTS { + let cert = rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( + cert.subject, + cert.spki, + cert.name_constraints, + ); + let certs = vec![cert].into_iter(); + root_certs.add_trust_anchors(certs); + } + + root_certs +} + +/// Connector service factory using `rustls`. +#[derive(Clone)] +pub struct TlsConnector { + connector: Arc, +} + +impl TlsConnector { + /// Constructs new connector service factory from a `rustls` client configuration. + pub fn new(connector: Arc) -> Self { + TlsConnector { connector } + } + + /// Constructs new connector service from a `rustls` client configuration. + pub fn service(connector: Arc) -> TlsConnectorService { + TlsConnectorService { connector } + } +} + +impl ServiceFactory> for TlsConnector +where + R: Host, + IO: ActixStream + 'static, +{ + type Response = Connection>; + type Error = io::Error; + type Config = (); + type Service = TlsConnectorService; + type InitError = (); + type Future = Ready>; + + fn new_service(&self, _: ()) -> Self::Future { + ready(Ok(TlsConnectorService { + connector: self.connector.clone(), + })) + } +} + +/// Connector service using `rustls`. +#[derive(Clone)] +pub struct TlsConnectorService { + connector: Arc, +} + +impl Service> for TlsConnectorService +where + R: Host, + IO: ActixStream, +{ + type Response = Connection>; + type Error = io::Error; + type Future = ConnectFut; + + actix_service::always_ready!(); + + fn call(&self, connection: Connection) -> Self::Future { + tracing::trace!("TLS handshake start for: {:?}", connection.hostname()); + let (stream, connection) = connection.replace_io(()); + + match ServerName::try_from(connection.hostname()) { + Ok(host) => ConnectFut::Future { + connect: RustlsTlsConnector::from(Arc::clone(&self.connector)) + .connect(host, stream), + connection: Some(connection), + }, + Err(_) => ConnectFut::InvalidServerName, + } + } +} + +/// Connect future for Rustls service. +#[doc(hidden)] +#[allow(clippy::large_enum_variant)] +pub enum ConnectFut { + InvalidServerName, + Future { + connect: RustlsConnect, + connection: Option>, + }, +} + +impl Future for ConnectFut +where + R: Host, + IO: ActixStream, +{ + type Output = io::Result>>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.get_mut() { + Self::InvalidServerName => Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "connection parameters specified invalid server name", + ))), + + Self::Future { + connect, + connection, + } => { + let stream = ready!(Pin::new(connect).poll(cx))?; + let connection = connection.take().unwrap(); + tracing::trace!("TLS handshake success: {:?}", connection.hostname()); + Poll::Ready(Ok(connection.replace_io(stream).1)) + } + } + } +} diff --git a/actix-tls/src/connect/rustls_0_22.rs b/actix-tls/src/connect/rustls_0_22.rs new file mode 100644 index 00000000..264258fe --- /dev/null +++ b/actix-tls/src/connect/rustls_0_22.rs @@ -0,0 +1,168 @@ +//! Rustls based connector service. +//! +//! See [`TlsConnector`] for main connector service factory docs. + +use core::future::{ready, Ready}; +use std::{ + future::Future, + io, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; + +use actix_rt::net::ActixStream; +use actix_service::{Service, ServiceFactory}; +use futures_core::ready; +use rustls_pki_types_1::ServerName; +use tokio_rustls::{ + client::TlsStream as AsyncTlsStream, rustls::ClientConfig, Connect as RustlsConnect, + TlsConnector as RustlsTlsConnector, +}; +use tokio_rustls_025 as tokio_rustls; + +use crate::connect::{Connection, Host}; + +pub mod reexports { + //! Re-exports from the `rustls` v0.22 ecosystem that are useful for connectors. + + pub use tokio_rustls_025::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig}; + #[cfg(feature = "rustls-0_22-webpki-roots")] + pub use webpki_roots_026::TLS_SERVER_ROOTS; +} + +/// Returns root certificates via `rustls-native-certs` crate as a rustls certificate store. +/// +/// See [`rustls_native_certs::load_native_certs()`] for more info on behavior and errors. +/// +/// [`rustls_native_certs::load_native_certs()`]: rustls_native_certs_08::load_native_certs() +#[cfg(feature = "rustls-0_22-native-roots")] +pub fn native_roots_cert_store() -> io::Result { + let mut root_certs = tokio_rustls::rustls::RootCertStore::empty(); + + let certs = rustls_native_certs_08::load_native_certs(); + if let Some(err) = certs.errors.into_iter().next() { + return Err(io::Error::other(err)); + } + + for cert in certs.certs { + root_certs.add(cert).unwrap(); + } + + Ok(root_certs) +} + +/// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store. +#[cfg(feature = "rustls-0_22-webpki-roots")] +pub fn webpki_roots_cert_store() -> tokio_rustls::rustls::RootCertStore { + let mut root_certs = tokio_rustls::rustls::RootCertStore::empty(); + root_certs.extend(webpki_roots_026::TLS_SERVER_ROOTS.to_owned()); + root_certs +} + +/// Connector service factory using `rustls`. +#[derive(Clone)] +pub struct TlsConnector { + connector: Arc, +} + +impl TlsConnector { + /// Constructs new connector service factory from a `rustls` client configuration. + pub fn new(connector: Arc) -> Self { + TlsConnector { connector } + } + + /// Constructs new connector service from a `rustls` client configuration. + pub fn service(connector: Arc) -> TlsConnectorService { + TlsConnectorService { connector } + } +} + +impl ServiceFactory> for TlsConnector +where + R: Host, + IO: ActixStream + 'static, +{ + type Response = Connection>; + type Error = io::Error; + type Config = (); + type Service = TlsConnectorService; + type InitError = (); + type Future = Ready>; + + fn new_service(&self, _: ()) -> Self::Future { + ready(Ok(TlsConnectorService { + connector: self.connector.clone(), + })) + } +} + +/// Connector service using `rustls`. +#[derive(Clone)] +pub struct TlsConnectorService { + connector: Arc, +} + +impl Service> for TlsConnectorService +where + R: Host, + IO: ActixStream, +{ + type Response = Connection>; + type Error = io::Error; + type Future = ConnectFut; + + actix_service::always_ready!(); + + fn call(&self, connection: Connection) -> Self::Future { + tracing::trace!("TLS handshake start for: {:?}", connection.hostname()); + let (stream, conn) = connection.replace_io(()); + + match ServerName::try_from(conn.hostname()) { + Ok(host) => ConnectFut::Future { + connect: RustlsTlsConnector::from(Arc::clone(&self.connector)) + .connect(host.to_owned(), stream), + connection: Some(conn), + }, + Err(_) => ConnectFut::InvalidServerName, + } + } +} + +/// Connect future for Rustls service. +#[doc(hidden)] +#[allow(clippy::large_enum_variant)] +pub enum ConnectFut { + InvalidServerName, + Future { + connect: RustlsConnect, + connection: Option>, + }, +} + +impl Future for ConnectFut +where + R: Host, + IO: ActixStream, +{ + type Output = io::Result>>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.get_mut() { + Self::InvalidServerName => Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "connection parameters specified invalid server name", + ))), + + Self::Future { + connect, + connection, + } => { + let stream = ready!(Pin::new(connect).poll(cx))?; + let connection = connection.take().unwrap(); + tracing::trace!("TLS handshake success: {:?}", connection.hostname()); + Poll::Ready(Ok(connection.replace_io(stream).1)) + } + } + } +} diff --git a/actix-tls/src/connect/rustls_0_23.rs b/actix-tls/src/connect/rustls_0_23.rs new file mode 100644 index 00000000..c3727f98 --- /dev/null +++ b/actix-tls/src/connect/rustls_0_23.rs @@ -0,0 +1,168 @@ +//! Rustls based connector service. +//! +//! See [`TlsConnector`] for main connector service factory docs. + +use core::future::{ready, Ready}; +use std::{ + future::Future, + io, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; + +use actix_rt::net::ActixStream; +use actix_service::{Service, ServiceFactory}; +use futures_core::ready; +use rustls_pki_types_1::ServerName; +use tokio_rustls::{ + client::TlsStream as AsyncTlsStream, rustls::ClientConfig, Connect as RustlsConnect, + TlsConnector as RustlsTlsConnector, +}; +use tokio_rustls_026 as tokio_rustls; + +use crate::connect::{Connection, Host}; + +pub mod reexports { + //! Re-exports from the `rustls` v0.23 ecosystem that are useful for connectors. + + pub use tokio_rustls_026::{client::TlsStream as AsyncTlsStream, rustls::ClientConfig}; + #[cfg(feature = "rustls-0_23-webpki-roots")] + pub use webpki_roots_026::TLS_SERVER_ROOTS; +} + +/// Returns root certificates via `rustls-native-certs` crate as a rustls certificate store. +/// +/// See [`rustls_native_certs::load_native_certs()`] for more info on behavior and errors. +/// +/// [`rustls_native_certs::load_native_certs()`]: rustls_native_certs_08::load_native_certs() +#[cfg(feature = "rustls-0_23-native-roots")] +pub fn native_roots_cert_store() -> io::Result { + let mut root_certs = tokio_rustls::rustls::RootCertStore::empty(); + + let certs = rustls_native_certs_08::load_native_certs(); + if let Some(err) = certs.errors.into_iter().next() { + return Err(io::Error::other(err)); + } + + for cert in certs.certs { + root_certs.add(cert).unwrap(); + } + + Ok(root_certs) +} + +/// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store. +#[cfg(feature = "rustls-0_23-webpki-roots")] +pub fn webpki_roots_cert_store() -> tokio_rustls::rustls::RootCertStore { + let mut root_certs = tokio_rustls::rustls::RootCertStore::empty(); + root_certs.extend(webpki_roots_026::TLS_SERVER_ROOTS.to_owned()); + root_certs +} + +/// Connector service factory using `rustls`. +#[derive(Clone)] +pub struct TlsConnector { + connector: Arc, +} + +impl TlsConnector { + /// Constructs new connector service factory from a `rustls` client configuration. + pub fn new(connector: Arc) -> Self { + TlsConnector { connector } + } + + /// Constructs new connector service from a `rustls` client configuration. + pub fn service(connector: Arc) -> TlsConnectorService { + TlsConnectorService { connector } + } +} + +impl ServiceFactory> for TlsConnector +where + R: Host, + IO: ActixStream + 'static, +{ + type Response = Connection>; + type Error = io::Error; + type Config = (); + type Service = TlsConnectorService; + type InitError = (); + type Future = Ready>; + + fn new_service(&self, _: ()) -> Self::Future { + ready(Ok(TlsConnectorService { + connector: self.connector.clone(), + })) + } +} + +/// Connector service using `rustls`. +#[derive(Clone)] +pub struct TlsConnectorService { + connector: Arc, +} + +impl Service> for TlsConnectorService +where + R: Host, + IO: ActixStream, +{ + type Response = Connection>; + type Error = io::Error; + type Future = ConnectFut; + + actix_service::always_ready!(); + + fn call(&self, connection: Connection) -> Self::Future { + tracing::trace!("TLS handshake start for: {:?}", connection.hostname()); + let (stream, conn) = connection.replace_io(()); + + match ServerName::try_from(conn.hostname()) { + Ok(host) => ConnectFut::Future { + connect: RustlsTlsConnector::from(Arc::clone(&self.connector)) + .connect(host.to_owned(), stream), + connection: Some(conn), + }, + Err(_) => ConnectFut::InvalidServerName, + } + } +} + +/// Connect future for Rustls service. +#[doc(hidden)] +#[allow(clippy::large_enum_variant)] +pub enum ConnectFut { + InvalidServerName, + Future { + connect: RustlsConnect, + connection: Option>, + }, +} + +impl Future for ConnectFut +where + R: Host, + IO: ActixStream, +{ + type Output = io::Result>>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.get_mut() { + Self::InvalidServerName => Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "connection parameters specified invalid server name", + ))), + + Self::Future { + connect, + connection, + } => { + let stream = ready!(Pin::new(connect).poll(cx))?; + let connection = connection.take().unwrap(); + tracing::trace!("TLS handshake success: {:?}", connection.hostname()); + Poll::Ready(Ok(connection.replace_io(stream).1)) + } + } + } +} diff --git a/actix-tls/src/connect/tcp.rs b/actix-tls/src/connect/tcp.rs index f8f0d3be..0aee74d4 100644 --- a/actix-tls/src/connect/tcp.rs +++ b/actix-tls/src/connect/tcp.rs @@ -2,6 +2,7 @@ //! //! See [`TcpConnector`] for main connector service factory docs. +use core::future::{ready, Ready}; use std::{ collections::VecDeque, future::Future, @@ -13,10 +14,9 @@ use std::{ use actix_rt::net::{TcpSocket, TcpStream}; use actix_service::{Service, ServiceFactory}; -use actix_utils::future::{ok, Ready}; use futures_core::ready; -use log::{error, trace}; use tokio_util::sync::ReusableBoxFuture; +use tracing::{error, trace}; use super::{connect_addrs::ConnectAddrs, error::ConnectError, ConnectInfo, Connection, Host}; @@ -41,7 +41,7 @@ impl ServiceFactory> for TcpConnector { type Future = Ready>; fn new_service(&self, _: ()) -> Self::Future { - ok(self.service()) + ready(Ok(self.service())) } } @@ -79,7 +79,7 @@ pub enum TcpConnectorFut { port: u16, local_addr: Option, addrs: Option>, - stream: ReusableBoxFuture>, + stream: ReusableBoxFuture<'static, Result>, }, Error(Option), @@ -114,8 +114,8 @@ impl TcpConnectorFut { stream: ReusableBoxFuture::new(connect(addr, local_addr)), }, - // when resolver returns multiple socket addr for request they would be popped from - // front end of queue and returns with the first successful tcp connection. + // When resolver returns multiple socket addr for request they would be popped from + // front end of queue and returns with the first successful TCP connection. ConnectAddrs::Multi(mut addrs) => { let addr = addrs.pop_front().unwrap(); diff --git a/actix-tls/src/connect/uri.rs b/actix-tls/src/connect/uri.rs index b1c7f0fe..6bdb9e5d 100644 --- a/actix-tls/src/connect/uri.rs +++ b/actix-tls/src/connect/uri.rs @@ -1,8 +1,19 @@ -use http::Uri; - use super::Host; -impl Host for Uri { +impl Host for http_0_2::Uri { + fn hostname(&self) -> &str { + self.host().unwrap_or("") + } + + fn port(&self) -> Option { + match self.port_u16() { + Some(port) => Some(port), + None => scheme_to_port(self.scheme_str()), + } + } +} + +impl Host for http_1::Uri { fn hostname(&self) -> &str { self.host().unwrap_or("") } diff --git a/actix-tls/src/impl_more.rs b/actix-tls/src/impl_more.rs deleted file mode 100644 index c380228b..00000000 --- a/actix-tls/src/impl_more.rs +++ /dev/null @@ -1,40 +0,0 @@ -/// A helper to implement `Deref` for a type. -#[macro_export] -macro_rules! deref { - ($ty:ident $(<$($generic:ident),*>)? => $field:tt: $target:ty) => { - impl $(<$($generic),*>)? ::core::ops::Deref for $ty $(<$($generic),*>)? { - type Target = $target; - - fn deref(&self) -> &Self::Target { - &self.$field - } - } - }; -} - -/// A helper to implement `DerefMut` for a type. -#[macro_export] -macro_rules! deref_mut { - ($ty:ident $(<$($generic:ident),*>)? => $field:tt) => { - impl $(<$($generic),*>)? ::core::ops::DerefMut for $ty $(<$($generic),*>)? { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.$field - } - } - }; -} - -/// A helper to implement `From` for a unit struct. -#[macro_export] -macro_rules! from { - ($from:ty => $ty:ident $(<$($generic:ident),*>)?) => { - impl $(<$($generic),*>)? ::core::convert::From<$from> for $ty $(<$($generic),*>)? { - fn from(from: $from) -> Self { - Self(from) - } - } - }; -} - -#[allow(unused_imports)] -pub(crate) use crate::{deref, deref_mut, from}; diff --git a/actix-tls/src/lib.rs b/actix-tls/src/lib.rs index dfca00cd..5a0406c2 100644 --- a/actix-tls/src/lib.rs +++ b/actix-tls/src/lib.rs @@ -1,10 +1,7 @@ //! TLS acceptor and connector services for the Actix ecosystem. -#![deny(rust_2018_idioms, nonstandard_style)] -#![warn(future_incompatible, missing_docs)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -// enable unstable doc_cfg feature only on on docs.rs where nightly compiler is used #![cfg_attr(docsrs, feature(doc_cfg))] #[cfg(feature = "openssl")] @@ -12,11 +9,7 @@ extern crate tls_openssl as openssl; #[cfg(feature = "accept")] -#[cfg_attr(docsrs, doc(cfg(feature = "accept")))] pub mod accept; #[cfg(feature = "connect")] -#[cfg_attr(docsrs, doc(cfg(feature = "connect")))] pub mod connect; - -mod impl_more; diff --git a/actix-tls/tests/accept-openssl.rs b/actix-tls/tests/accept-openssl.rs index 4b6fadf0..8fae2c48 100644 --- a/actix-tls/tests/accept-openssl.rs +++ b/actix-tls/tests/accept-openssl.rs @@ -3,28 +3,30 @@ #![cfg(all( feature = "accept", feature = "connect", - feature = "rustls", + feature = "rustls-0_23", feature = "openssl" ))] -use std::{convert::TryFrom, io::Write, sync::Arc}; +use core::future::ready; +use std::{io::Write as _, sync::Arc}; use actix_rt::net::TcpStream; use actix_server::TestServer; use actix_service::ServiceFactoryExt as _; -use actix_tls::accept::openssl::{Acceptor, TlsStream}; -use actix_utils::future::ok; -use tokio_rustls::rustls::{Certificate, ClientConfig, RootCertStore, ServerName}; +use actix_tls::{ + accept::openssl::{Acceptor, TlsStream}, + connect::rustls_0_23::reexports::ClientConfig, +}; +use rustls_pki_types_1::ServerName; +use tokio_rustls_026::rustls::RootCertStore; fn new_cert_and_key() -> (String, String) { - let cert = rcgen::generate_simple_self_signed(vec![ - "127.0.0.1".to_owned(), - "localhost".to_owned(), - ]) - .unwrap(); + let rcgen::CertifiedKey { cert, key_pair } = + rcgen::generate_simple_self_signed(vec!["127.0.0.1".to_owned(), "localhost".to_owned()]) + .unwrap(); - let key = cert.serialize_private_key_pem(); - let cert = cert.serialize_pem().unwrap(); + let key = key_pair.serialize_pem(); + let cert = cert.pem(); (cert, key) } @@ -47,30 +49,48 @@ fn openssl_acceptor(cert: String, key: String) -> tls_openssl::ssl::SslAcceptor builder.build() } -#[allow(dead_code)] mod danger { - use std::time::SystemTime; - - use super::*; - - use tokio_rustls::rustls::{ - self, - client::{ServerCertVerified, ServerCertVerifier}, - }; + use rustls_pki_types_1::{CertificateDer, ServerName, UnixTime}; + use tokio_rustls_026::rustls; + /// Disables certificate verification to allow self-signed certs from rcgen. + #[derive(Debug)] pub struct NoCertificateVerification; - impl ServerCertVerifier for NoCertificateVerification { + impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification { fn verify_server_cert( &self, - _end_entity: &Certificate, - _intermediates: &[Certificate], - _server_name: &ServerName, - _scts: &mut dyn Iterator, - _ocsp_response: &[u8], - _now: SystemTime, - ) -> Result { - Ok(ServerCertVerified::assertion()) + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp: &[u8], + _now: UnixTime, + ) -> Result { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &rustls_pki_types_1::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &rustls_pki_types_1::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + rustls::crypto::aws_lc_rs::default_provider() + .signature_verification_algorithms + .supported_schemes() } } } @@ -78,7 +98,6 @@ mod danger { #[allow(dead_code)] fn rustls_connector(_cert: String, _key: String) -> ClientConfig { let mut config = ClientConfig::builder() - .with_safe_defaults() .with_root_certificates(RootCertStore::empty()) .with_no_client_auth(); @@ -92,6 +111,10 @@ fn rustls_connector(_cert: String, _key: String) -> ClientConfig { #[actix_rt::test] async fn accepts_connections() { + tokio_rustls_026::rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .unwrap(); + let (cert, key) = new_cert_and_key(); let srv = TestServer::start({ @@ -103,8 +126,8 @@ async fn accepts_connections() { let tls_acceptor = Acceptor::new(openssl_acceptor); tls_acceptor - .map_err(|err| println!("OpenSSL error: {:?}", err)) - .and_then(move |_stream: TlsStream| ok(())) + .map_err(|err| println!("OpenSSL error: {err:?}")) + .and_then(move |_stream: TlsStream| ready(Ok(()))) } }); @@ -118,13 +141,13 @@ async fn accepts_connections() { let config = rustls_connector(cert, key); let config = Arc::new(config); - let mut conn = tokio_rustls::rustls::ClientConnection::new( + let mut conn = tokio_rustls_026::rustls::ClientConnection::new( config, ServerName::try_from("localhost").unwrap(), ) .unwrap(); - let mut stream = tokio_rustls::rustls::Stream::new(&mut conn, &mut sock); + let mut stream = tokio_rustls_026::rustls::Stream::new(&mut conn, &mut sock); stream.flush().expect("TLS handshake failed"); } diff --git a/actix-tls/tests/accept-rustls.rs b/actix-tls/tests/accept-rustls.rs index 521af7f9..e04542ff 100644 --- a/actix-tls/tests/accept-rustls.rs +++ b/actix-tls/tests/accept-rustls.rs @@ -3,50 +3,51 @@ #![cfg(all( feature = "accept", feature = "connect", - feature = "rustls", + feature = "rustls-0_23", feature = "openssl" ))] extern crate tls_openssl as openssl; +use core::future::ready; use std::io::{BufReader, Write}; use actix_rt::net::TcpStream; use actix_server::TestServer; use actix_service::ServiceFactoryExt as _; -use actix_tls::accept::rustls::{Acceptor, TlsStream}; -use actix_tls::connect::openssl::reexports::SslConnector; -use actix_utils::future::ok; +use actix_tls::{ + accept::rustls_0_23::{reexports::ServerConfig, Acceptor, TlsStream}, + connect::openssl::reexports::SslConnector, +}; use rustls_pemfile::{certs, pkcs8_private_keys}; +use rustls_pki_types_1::PrivateKeyDer; use tls_openssl::ssl::SslVerifyMode; -use tokio_rustls::rustls::{self, Certificate, PrivateKey, ServerConfig}; fn new_cert_and_key() -> (String, String) { - let cert = rcgen::generate_simple_self_signed(vec![ - "127.0.0.1".to_owned(), - "localhost".to_owned(), - ]) - .unwrap(); + let rcgen::CertifiedKey { cert, key_pair } = + rcgen::generate_simple_self_signed(vec!["127.0.0.1".to_owned(), "localhost".to_owned()]) + .unwrap(); - let key = cert.serialize_private_key_pem(); - let cert = cert.serialize_pem().unwrap(); + let key = key_pair.serialize_pem(); + let cert = cert.pem(); (cert, key) } -fn rustls_server_config(cert: String, key: String) -> rustls::ServerConfig { +fn rustls_server_config(cert: String, key: String) -> ServerConfig { // Load TLS key and cert files let cert = &mut BufReader::new(cert.as_bytes()); let key = &mut BufReader::new(key.as_bytes()); - let cert_chain = certs(cert).unwrap().into_iter().map(Certificate).collect(); - let mut keys = pkcs8_private_keys(key).unwrap(); + let cert_chain = certs(cert).collect::, _>>().unwrap(); + let mut keys = pkcs8_private_keys(key) + .collect::, _>>() + .unwrap(); let mut config = ServerConfig::builder() - .with_safe_defaults() .with_no_client_auth() - .with_single_cert(cert_chain, PrivateKey(keys.remove(0))) + .with_single_cert(cert_chain, PrivateKeyDer::Pkcs8(keys.remove(0))) .unwrap(); config.alpn_protocols = vec![b"http/1.1".to_vec()]; @@ -72,6 +73,10 @@ fn openssl_connector(cert: String, key: String) -> SslConnector { #[actix_rt::test] async fn accepts_connections() { + tokio_rustls_026::rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .unwrap(); + let (cert, key) = new_cert_and_key(); let srv = TestServer::start({ @@ -82,8 +87,8 @@ async fn accepts_connections() { let tls_acceptor = Acceptor::new(rustls_server_config(cert.clone(), key.clone())); tls_acceptor - .map_err(|err| println!("Rustls error: {:?}", err)) - .and_then(move |_stream: TlsStream| ok(())) + .map_err(|err| println!("Rustls error: {err:?}")) + .and_then(move |_stream: TlsStream| ready(Ok(()))) } }); diff --git a/actix-tls/tests/test_connect.rs b/actix-tls/tests/test_connect.rs index bdd6b49c..c6885716 100644 --- a/actix-tls/tests/test_connect.rs +++ b/actix-tls/tests/test_connect.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] #![cfg(feature = "connect")] use std::{ @@ -9,10 +10,9 @@ use actix_codec::{BytesCodec, Framed}; use actix_rt::net::TcpStream; use actix_server::TestServer; use actix_service::{fn_service, Service, ServiceFactory}; -use bytes::Bytes; -use futures_util::sink::SinkExt; - use actix_tls::connect::{ConnectError, ConnectInfo, Connection, Connector, Host}; +use bytes::Bytes; +use futures_util::sink::SinkExt as _; #[cfg(feature = "openssl")] #[actix_rt::test] @@ -31,7 +31,7 @@ async fn test_string() { assert_eq!(con.peer_addr().unwrap(), srv.addr()); } -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-0_23")] #[actix_rt::test] async fn test_rustls_string() { let srv = TestServer::start(|| { @@ -99,8 +99,6 @@ async fn service_factory() { #[cfg(all(feature = "openssl", feature = "uri"))] #[actix_rt::test] async fn test_openssl_uri() { - use std::convert::TryFrom; - let srv = TestServer::start(|| { fn_service(|io: TcpStream| async { let mut framed = Framed::new(io, BytesCodec); @@ -110,16 +108,14 @@ async fn test_openssl_uri() { }); let connector = Connector::default().service(); - let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap(); + let addr = http_0_2::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap(); let con = connector.call(addr.into()).await.unwrap(); assert_eq!(con.peer_addr().unwrap(), srv.addr()); } -#[cfg(all(feature = "rustls", feature = "uri"))] +#[cfg(all(feature = "rustls-0_23", feature = "uri"))] #[actix_rt::test] -async fn test_rustls_uri() { - use std::convert::TryFrom; - +async fn test_rustls_uri_http1() { let srv = TestServer::start(|| { fn_service(|io: TcpStream| async { let mut framed = Framed::new(io, BytesCodec); @@ -129,7 +125,24 @@ async fn test_rustls_uri() { }); let conn = Connector::default().service(); - let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap(); + let addr = http_1::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap(); + let con = conn.call(addr.into()).await.unwrap(); + assert_eq!(con.peer_addr().unwrap(), srv.addr()); +} + +#[cfg(all(feature = "rustls-0_23", feature = "uri"))] +#[actix_rt::test] +async fn test_rustls_uri() { + let srv = TestServer::start(|| { + fn_service(|io: TcpStream| async { + let mut framed = Framed::new(io, BytesCodec); + framed.send(Bytes::from_static(b"test")).await?; + Ok::<_, io::Error>(()) + }) + }); + + let conn = Connector::default().service(); + let addr = http_1::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap(); let con = conn.call(addr.into()).await.unwrap(); assert_eq!(con.peer_addr().unwrap(), srv.addr()); } @@ -144,6 +157,9 @@ async fn test_local_addr() { }) }); + // if you've arrived here because of a failing test on macOS run this in your terminal: + // sudo ifconfig lo0 alias 127.0.0.3 + let conn = Connector::default().service(); let local = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 3)); diff --git a/actix-tls/tests/test_resolvers.rs b/actix-tls/tests/test_resolvers.rs index 81bbec54..7a5beafd 100644 --- a/actix-tls/tests/test_resolvers.rs +++ b/actix-tls/tests/test_resolvers.rs @@ -1,3 +1,4 @@ +#![allow(missing_docs)] #![cfg(feature = "connect")] use std::{ @@ -8,11 +9,10 @@ use std::{ use actix_rt::net::TcpStream; use actix_server::TestServer; use actix_service::{fn_service, Service, ServiceFactory}; -use futures_core::future::LocalBoxFuture; - use actix_tls::connect::{ ConnectError, ConnectInfo, Connection, Connector, Host, Resolve, Resolver, }; +use futures_core::future::LocalBoxFuture; #[actix_rt::test] async fn custom_resolver() { @@ -26,7 +26,7 @@ async fn custom_resolver() { port: u16, ) -> LocalBoxFuture<'a, Result, Box>> { Box::pin(async move { - let local = format!("127.0.0.1:{}", port).parse().unwrap(); + let local = format!("127.0.0.1:{port}").parse().unwrap(); Ok(vec![local]) }) } @@ -50,13 +50,12 @@ async fn custom_resolver_connect() { Connector::new(resolver) } - use trust_dns_resolver::TokioAsyncResolver; + use hickory_resolver::TokioResolver; - let srv = - TestServer::start(|| fn_service(|_io: TcpStream| async { Ok::<_, io::Error>(()) })); + let srv = TestServer::start(|| fn_service(|_io: TcpStream| async { Ok::<_, io::Error>(()) })); struct MyResolver { - trust_dns: TokioAsyncResolver, + hickory_dns: TokioResolver, } impl Resolve for MyResolver { @@ -67,7 +66,7 @@ async fn custom_resolver_connect() { ) -> LocalBoxFuture<'a, Result, Box>> { Box::pin(async move { let res = self - .trust_dns + .hickory_dns .lookup_ip(host) .await? .iter() @@ -79,7 +78,7 @@ async fn custom_resolver_connect() { } let resolver = MyResolver { - trust_dns: TokioAsyncResolver::tokio_from_system_conf().unwrap(), + hickory_dns: TokioResolver::builder_tokio().unwrap().build(), }; let factory = connector_factory(Resolver::custom(resolver)); diff --git a/actix-tracing/CHANGES.md b/actix-tracing/CHANGES.md index 7bded322..4e907181 100644 --- a/actix-tracing/CHANGES.md +++ b/actix-tracing/CHANGES.md @@ -1,5 +1,10 @@ # Changes -## [0.1.0] - 2020-01-15 +## Unreleased + +- Minimum supported Rust version (MSRV) is now 1.88. +- internal: Use `core::future::{ready, Ready}` instead of actix-utils' + +## 0.1.0 - Initial release diff --git a/actix-tracing/Cargo.toml b/actix-tracing/Cargo.toml index 2ed2c434..21413cd3 100644 --- a/actix-tracing/Cargo.toml +++ b/actix-tracing/Cargo.toml @@ -5,23 +5,25 @@ authors = ["Rajasekharan Vengalil "] description = "Support for tokio tracing with Actix services" keywords = ["network", "framework", "tracing"] homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-net.git" +repository = "https://github.com/actix/actix-net/tree/main/actix-tracing" documentation = "https://docs.rs/actix-tracing" categories = ["network-programming", "asynchronous"] license = "MIT OR Apache-2.0" -edition = "2018" +edition.workspace = true +rust-version.workspace = true -[lib] -name = "actix_tracing" -path = "src/lib.rs" +[package.metadata.cargo_check_external_types] +allowed_external_types = ["actix_service::*", "actix_utils::*", "tracing::*", "tracing_futures::*"] [dependencies] -actix-service = "2.0.0" -actix-utils = "3.0.0" - -tracing = "0.1" +actix-service = "2" +actix-utils = "3" +tracing = "0.1.35" tracing-futures = "0.2" -[dev_dependencies] -actix-rt = "2.0.0" +[dev-dependencies] +actix-rt = "2" slab = "0.4" + +[lints] +workspace = true diff --git a/actix-tracing/src/lib.rs b/actix-tracing/src/lib.rs index a12f6d05..d7b072a4 100644 --- a/actix-tracing/src/lib.rs +++ b/actix-tracing/src/lib.rs @@ -1,16 +1,17 @@ //! Actix tracing - support for tokio tracing with Actix services. -#![deny(rust_2018_idioms, nonstandard_style)] -#![warn(future_incompatible)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -use core::marker::PhantomData; +use core::{ + future::{ready, Ready}, + marker::PhantomData, +}; use actix_service::{ apply, ApplyTransform, IntoServiceFactory, Service, ServiceFactory, Transform, }; -use actix_utils::future::{ok, Either, Ready}; +use actix_utils::future::Either; use tracing_futures::{Instrument, Instrumented}; /// A `Service` implementation that automatically enters/exits tracing spans @@ -22,6 +23,7 @@ pub struct TracingService { } impl TracingService { + /// Constructs new tracing middleware. pub fn new(inner: S, make_span: F) -> Self { TracingService { inner, make_span } } @@ -63,6 +65,7 @@ pub struct TracingTransform { } impl TracingTransform { + /// Constructs new tracing middleware. pub fn new(make_span: F) -> Self { TracingTransform { make_span, @@ -84,7 +87,7 @@ where type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - ok(TracingService::new(service, self.make_span.clone())) + ready(Ok(TracingService::new(service, self.make_span.clone()))) } } @@ -118,18 +121,21 @@ where #[cfg(test)] mod test { - use super::*; - - use std::cell::RefCell; - use std::collections::{BTreeMap, BTreeSet}; - use std::sync::{Arc, RwLock}; + use core::future::ready; + use std::{ + cell::RefCell, + collections::{BTreeMap, BTreeSet}, + sync::{Arc, RwLock}, + }; use actix_service::{fn_factory, fn_service}; use slab::Slab; use tracing::{span, Event, Level, Metadata, Subscriber}; + use super::*; + thread_local! { - static SPAN: RefCell> = RefCell::new(Vec::new()); + static SPAN: RefCell> = const { RefCell::new(Vec::new()) }; } #[derive(Default)] @@ -219,10 +225,10 @@ mod test { #[actix_rt::test] async fn service_call() { let service_factory = fn_factory(|| { - ok::<_, ()>(fn_service(|req: &'static str| { + ready(Ok::<_, ()>(fn_service(|req: &'static str| { tracing::event!(Level::TRACE, "It's happening - {}!", req); - ok::<_, ()>(()) - })) + ready(Ok::<_, ()>(())) + }))) }); let subscriber = TestSubscriber::default(); diff --git a/actix-utils/CHANGES.md b/actix-utils/CHANGES.md index 8c86e646..ca21abb8 100644 --- a/actix-utils/CHANGES.md +++ b/actix-utils/CHANGES.md @@ -1,19 +1,26 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased +- Minimum supported Rust version (MSRV) is now 1.88. +- Deprecate `crate::ready::*` in favor of `core::future::{ready, Ready}` + +## 3.0.1 + +- Minimum supported Rust version (MSRV) is now 1.57. + +## 3.0.0 -## 3.0.0 - 2021-04-16 - No significant changes from `3.0.0-beta.4`. +## 3.0.0-beta.4 -## 3.0.0-beta.4 - 2021-04-01 - Add `future::Either` type. [#305] [#305]: https://github.com/actix/actix-net/pull/305 +## 3.0.0-beta.3 -## 3.0.0-beta.3 - 2021-04-01 - Moved `mpsc` to own crate `local-channel`. [#301] - Moved `task::LocalWaker` to own crate `local-waker`. [#301] - Remove `timeout` module. [#301] @@ -22,14 +29,14 @@ [#301]: https://github.com/actix/actix-net/pull/301 +## 3.0.0-beta.2 -## 3.0.0-beta.2 - 2021-02-06 - Update `actix-rt` to `2.0.0`. [#273] [#273]: https://github.com/actix/actix-net/pull/273 +## 3.0.0-beta.1 -## 3.0.0-beta.1 - 2020-12-28 - Update `bytes` dependency to `1`. [#237] - Use `pin-project-lite` to replace `pin-project`. [#229] - Remove `condition`,`either`,`inflight`,`keepalive`,`oneshot`,`order`,`stream` and `time` mods. [#229] @@ -37,146 +44,146 @@ [#229]: https://github.com/actix/actix-net/pull/229 [#237]: https://github.com/actix/actix-net/pull/237 +## 2.0.0 -## 2.0.0 - 2020-08-23 - No changes from beta 1. +## 2.0.0-beta.1 -## 2.0.0-beta.1 - 2020-08-19 - Upgrade `tokio-util` to `0.3`. - Remove unsound custom Cell and use `std::cell::RefCell` instead, as well as `actix-service`. - Rename method to correctly spelled `LocalWaker::is_registered`. +## 1.0.6 -## 1.0.6 - 2020-01-08 - Add `Clone` impl for `condition::Waiter`. +## 1.0.5 -## 1.0.5 - 2020-01-08 - Add `Condition` type. - Add `Pool` of one-shot's. +## 1.0.4 -## 1.0.4 - 2019-12-20 - Add methods to check `LocalWaker` registration state. +## 1.0.3 -## 1.0.3 - 2019-12-11 - Revert InOrder service changes +## 1.0.2 -## 1.0.2 - 2019-12-11 - Allow to create `framed::Dispatcher` with custom `mpsc::Receiver`. - Add `oneshot::Sender::is_canceled()` method. +## 1.0.1 -## 1.0.1 - 2019-12-11 - Optimize InOrder service. +## 1.0.0 -## 1.0.0 - 2019-12-11 - Simplify oneshot and mpsc implementations. +## 1.0.0-alpha.3 -## 1.0.0-alpha.3 - 2019-12-07 - Migrate to tokio 0.2. - Fix oneshot. +## 1.0.0-alpha.2 -## 1.0.0-alpha.2 - 2019-12-02 - Migrate to `std::future`. +## 0.4.7 -## 0.4.7 - 2019-10-14 - Re-register task on every framed transport poll. +## 0.4.6 -## 0.4.6 - 2019-10-08 - Refactor `Counter` type. register current task in available method. +## 0.4.5 -## 0.4.5 - 2019-07-19 - Deprecated `CloneableService` as it is not safe. +## 0.4.4 -## 0.4.4 - 2019-07-17 - Undeprecate `FramedTransport` as it is actually useful. +## 0.4.3 -## 0.4.3 - 2019-07-17 - Deprecate `CloneableService` as it is not safe and in general not very useful. - Deprecate `FramedTransport` in favor of `actix-ioframe`. +## 0.4.2 -## 0.4.2 - 2019-06-26 - Do not block on sink drop for FramedTransport. +## 0.4.1 -## 0.4.1 - 2019-05-15 - Change `Either` constructor. +## 0.4.0 -## 0.4.0 - 2019-05-11 - Change `Either` to handle two nexted services. - Upgrade actix-service 0.4. - Removed framed related services. - Removed stream related services. +## 0.3.5 -## 0.3.5 - 2019-04-04 - Allow to send messages to `FramedTransport` via mpsc channel. - Remove `'static` constraint from Clonable service. +## 0.3.4 -## 0.3.4 - 2019-03-12 - `TimeoutService`, `InOrderService`, `InFlightService` accepts generic IntoService services. - Fix `InFlightService::poll_ready()` nested service readiness check. - Fix `InOrderService::poll_ready()` nested service readiness check. +## 0.3.3 -## 0.3.3 - 2019-03-09 - Revert IntoFuture change. - Add generic config param for IntoFramed and TakeOne new services. +## 0.3.2 -## 0.3.2 - 2019-03-04 - Use IntoFuture for new services. -## 0.3.1 - 2019-03-04 +## 0.3.1 + - Use new type of transform trait. +## 0.3.0 -## 0.3.0 - 2019-03-02 - Use new `NewService` trait -- BoxedNewService` and `BoxedService` types moved to actix-service crate. +- BoxedNewService`and`BoxedService` types moved to actix-service crate. +## 0.2.4 -## 0.2.4 - 2019-02-21 - Custom `BoxedNewService` implementation. +## 0.2.3 -## 0.2.3 - 2019-02-21 - Add `BoxedNewService` and `BoxedService`. +## 0.2.2 -## 0.2.2 - 2019-02-11 - Add `Display` impl for `TimeoutError`. - Add `Display` impl for `InOrderError`. +## 0.2.1 -## 0.2.1 - 2019-02-06 -- Add `InOrder` service. the service yields responses as they become available, - in the order that their originating requests were submitted to the service. +- Add `InOrder` service. the service yields responses as they become available, in the order that their originating requests were submitted to the service. - Convert `Timeout` and `InFlight` services to a transforms. +## 0.2.0 -## 0.2.0 - 2019-02-01 - Fix framed transport error handling. - Added Clone impl for Either service. - Added Clone impl for Timeout service factory. - Added Service and NewService for Stream dispatcher. - Switch to actix-service 0.2. +## 0.1.0 -## 0.1.0 - 2018-12-09 - Move utils services to separate crate. diff --git a/actix-utils/Cargo.toml b/actix-utils/Cargo.toml index ed858378..83340f52 100644 --- a/actix-utils/Cargo.toml +++ b/actix-utils/Cargo.toml @@ -1,26 +1,23 @@ [package] name = "actix-utils" -version = "3.0.0" -authors = [ - "Nikolay Kim ", - "Rob Ede ", -] +version = "3.0.1" +authors = ["Nikolay Kim ", "Rob Ede "] description = "Various utilities used in the Actix ecosystem" keywords = ["network", "framework", "async", "futures"] categories = ["network-programming", "asynchronous"] repository = "https://github.com/actix/actix-net" license = "MIT OR Apache-2.0" -edition = "2018" - -[lib] -name = "actix_utils" -path = "src/lib.rs" +edition.workspace = true +rust-version.workspace = true [dependencies] -pin-project-lite = "0.2" local-waker = "0.1" +pin-project-lite = "0.2" [dev-dependencies] -actix-rt = "2.0.0" -futures-util = { version = "0.3.7", default-features = false } +actix-rt = "2" +futures-util = { version = "0.3.17", default-features = false } static_assertions = "1.1" + +[lints] +workspace = true diff --git a/actix-utils/src/counter.rs b/actix-utils/src/counter.rs index a61ef90e..4259fd6d 100644 --- a/actix-utils/src/counter.rs +++ b/actix-utils/src/counter.rs @@ -29,7 +29,7 @@ impl Counter { /// Returns true if counter is below capacity. Otherwise, register to wake task when it is. #[inline] - pub fn available(&self, cx: &mut task::Context<'_>) -> bool { + pub fn available(&self, cx: &task::Context<'_>) -> bool { self.0.available(cx) } @@ -59,7 +59,7 @@ impl CounterInner { } } - fn available(&self, cx: &mut task::Context<'_>) -> bool { + fn available(&self, cx: &task::Context<'_>) -> bool { if self.count.get() < self.capacity { true } else { diff --git a/actix-utils/src/future/either.rs b/actix-utils/src/future/either.rs index 78582954..018de24e 100644 --- a/actix-utils/src/future/either.rs +++ b/actix-utils/src/future/either.rs @@ -15,7 +15,8 @@ pin_project! { /// /// # Examples /// ``` - /// use actix_utils::future::{ready, Ready, Either}; + /// use actix_utils::future::Either; + /// use core::future::{ready, Ready}; /// /// # async fn run() { /// let res = Either::<_, Ready>::left(ready(42)); @@ -81,8 +82,9 @@ where #[cfg(test)] mod tests { + use core::future::{ready, Ready}; + use super::*; - use crate::future::{ready, Ready}; #[actix_rt::test] async fn test_either() { diff --git a/actix-utils/src/future/mod.rs b/actix-utils/src/future/mod.rs index be3807bf..d03ac061 100644 --- a/actix-utils/src/future/mod.rs +++ b/actix-utils/src/future/mod.rs @@ -1,9 +1,12 @@ -//! Asynchronous values. +//! Helpers for constructing futures. mod either; mod poll_fn; mod ready; -pub use self::either::Either; -pub use self::poll_fn::{poll_fn, PollFn}; +#[allow(deprecated)] pub use self::ready::{err, ok, ready, Ready}; +pub use self::{ + either::Either, + poll_fn::{poll_fn, PollFn}, +}; diff --git a/actix-utils/src/future/poll_fn.rs b/actix-utils/src/future/poll_fn.rs index 31421b45..09638bcf 100644 --- a/actix-utils/src/future/poll_fn.rs +++ b/actix-utils/src/future/poll_fn.rs @@ -8,6 +8,31 @@ use core::{ }; /// Creates a future driven by the provided function that receives a task context. +/// +/// # Examples +/// ``` +/// # use std::task::Poll; +/// # use actix_utils::future::poll_fn; +/// # async fn test_poll_fn() { +/// let res = poll_fn(|_| Poll::Ready(42)).await; +/// assert_eq!(res, 42); +/// +/// let mut i = 5; +/// let res = poll_fn(|cx| { +/// i -= 1; +/// +/// if i > 0 { +/// cx.waker().wake_by_ref(); +/// Poll::Pending +/// } else { +/// Poll::Ready(42) +/// } +/// }) +/// .await; +/// assert_eq!(res, 42); +/// # } +/// # actix_rt::Runtime::new().unwrap().block_on(test_poll_fn()); +/// ``` #[inline] pub fn poll_fn(f: F) -> PollFn where @@ -16,13 +41,11 @@ where PollFn { f } } -/// A Future driven by the inner function. +/// Future for the [`poll_fn`] function. pub struct PollFn { f: F, } -impl Unpin for PollFn {} - impl fmt::Debug for PollFn { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PollFn").finish() @@ -36,15 +59,23 @@ where type Output = T; #[inline] - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - (self.f)(cx) + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // SAFETY: we are not moving out of the pinned field + // see https://github.com/rust-lang/rust/pull/102737 + #[allow(clippy::needless_borrow)] + (unsafe { &mut self.get_unchecked_mut().f })(cx) } } #[cfg(test)] mod tests { + use std::marker::PhantomPinned; + use super::*; + static_assertions::assert_impl_all!(PollFn<()>: Unpin); + static_assertions::assert_not_impl_all!(PollFn: Unpin); + #[actix_rt::test] async fn test_poll_fn() { let res = poll_fn(|_| Poll::Ready(42)).await; @@ -64,4 +95,30 @@ mod tests { .await; assert_eq!(res, 42); } + + // following soundness tests taken from https://github.com/tokio-rs/tokio/pull/5087 + + #[allow(dead_code)] + fn require_send(_t: &T) {} + #[allow(dead_code)] + fn require_sync(_t: &T) {} + + #[allow(unused)] + trait AmbiguousIfUnpin { + fn some_item(&self) {} + } + impl AmbiguousIfUnpin<()> for T {} + impl AmbiguousIfUnpin<[u8; 0]> for T {} + + const _: fn() = || { + let pinned = std::marker::PhantomPinned; + let f = poll_fn(move |_| { + // Use `pinned` to take ownership of it. + let _ = &pinned; + std::task::Poll::Pending::<()> + }); + require_send(&f); + require_sync(&f); + AmbiguousIfUnpin::some_item(&f); + }; } diff --git a/actix-utils/src/future/ready.rs b/actix-utils/src/future/ready.rs index cd375c1f..f8dfe22e 100644 --- a/actix-utils/src/future/ready.rs +++ b/actix-utils/src/future/ready.rs @@ -1,4 +1,6 @@ -//! When MSRV is 1.48, replace with `core::future::Ready` and `core::future::ready()`. +//! Deprecated. Use `core::future::Ready` instead, it has the same functionality. + +#![allow(deprecated)] use core::{ future::Future, @@ -6,7 +8,7 @@ use core::{ task::{Context, Poll}, }; -/// Future for the [`ready`](ready()) function. +/// Future for the [`ready`] function. /// /// Panic will occur if polled more than once. /// @@ -26,6 +28,7 @@ use core::{ /// ``` #[derive(Debug, Clone)] #[must_use = "futures do nothing unless you `.await` or poll them"] +#[deprecated(since = "3.0.2", note = "Use `core::future::Ready` instead.")] pub struct Ready { val: Option, } @@ -66,6 +69,7 @@ impl Future for Ready { /// assert_eq!(a.into_inner(), 1); /// ``` #[inline] +#[deprecated(since = "3.0.2", note = "Use `core::future::ready(val)` instead.")] pub fn ready(val: T) -> Ready { Ready { val: Some(val) } } @@ -82,6 +86,7 @@ pub fn ready(val: T) -> Ready { /// # } /// ``` #[inline] +#[deprecated(since = "3.0.2", note = "Use `core::future::ready(Ok(val))` instead.")] pub fn ok(val: T) -> Ready> { Ready { val: Some(Ok(val)) } } @@ -98,6 +103,7 @@ pub fn ok(val: T) -> Ready> { /// # } /// ``` #[inline] +#[deprecated(since = "3.0.2", note = "Use `core::future::ready(Err(err))` instead.")] pub fn err(err: E) -> Ready> { Ready { val: Some(Err(err)), @@ -105,6 +111,7 @@ pub fn err(err: E) -> Ready> { } #[cfg(test)] +#[allow(deprecated)] mod tests { use std::rc::Rc; diff --git a/actix-utils/src/lib.rs b/actix-utils/src/lib.rs index b02687cb..5abc75f8 100644 --- a/actix-utils/src/lib.rs +++ b/actix-utils/src/lib.rs @@ -1,7 +1,5 @@ //! Various utilities used in the Actix ecosystem. -#![deny(rust_2018_idioms, nonstandard_style)] -#![warn(future_incompatible, missing_docs)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/bytestring/CHANGES.md b/bytestring/CHANGES.md index 707a6f4a..b29e3e72 100644 --- a/bytestring/CHANGES.md +++ b/bytestring/CHANGES.md @@ -1,36 +1,78 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased +- Minimum supported Rust version (MSRV) is now 1.88. + +## 1.5.0 + +- Migrate `serde` dependency to `serde_core`. +- Minimum supported Rust version (MSRV) is now 1.75. +- Switch `serde` to `serde_core` + +## 1.4.0 + +- Add `ByteString::split_at()` method. +- Minimum supported Rust version (MSRV) is now 1.71. + +## 1.3.1 + +- No significant changes since `1.3.0`. + +## 1.3.0 + +- Implement `AsRef` for `ByteString`. + +## 1.2.1 + +- Fix `#[no_std]` compatibility. [#471] + +[#471]: https://github.com/actix/actix-net/pull/471 + +## 1.2.0 + +- Add `ByteString::slice_ref` which can safely slice a `ByteString` into a new one with zero copy. [#470] +- Minimum supported Rust version (MSRV) is now 1.57. + +[#470]: https://github.com/actix/actix-net/pull/470 + +## 1.1.0 + +- Implement `From>` for `ByteString`. [#458] +- Implement `Into` for `ByteString`. [#458] +- Minimum supported Rust version (MSRV) is now 1.49. + +[#458]: https://github.com/actix/actix-net/pull/458 + +## 1.0.0 -## 1.0.0 - 2020-12-31 - Update `bytes` dependency to `1`. - Add array and slice of `u8` impls of `TryFrom` up to 32 in length. - Rename `get_ref` to `as_bytes` and rename `into_inner` to `into_bytes`. - `ByteString::new` is now a `const fn`. - Crate is now `#[no_std]` compatible. +## 0.1.5 -## 0.1.5 - 2020-03-30 - Serde support +## 0.1.4 -## 0.1.4 - 2020-01-14 - Fix `AsRef` impl +## 0.1.3 -## 0.1.3 - 2020-01-13 - Add `PartialEq>`, `AsRef<[u8]>` impls +## 0.1.2 -## 0.1.2 - 2019-12-22 - Fix `new()` method - Make `ByteString::from_static()` and `ByteString::from_bytes_unchecked()` methods const. +## 0.1.1 -## 0.1.1 - 2019-12-07 - Fix hash impl +## 0.1.0 -## 0.1.0 - 2019-12-07 - Initial release diff --git a/bytestring/Cargo.toml b/bytestring/Cargo.toml index 6be9eed2..bbc64412 100644 --- a/bytestring/Cargo.toml +++ b/bytestring/Cargo.toml @@ -1,28 +1,30 @@ [package] name = "bytestring" -version = "1.0.0" -authors = [ - "Nikolay Kim ", - "Rob Ede ", -] -description = "An immutable UTF-8 encoded string using Bytes as storage" -keywords = ["string", "bytes", "utf8", "web", "actix"] +version = "1.5.0" +description = "A UTF-8 encoded read-only string using `Bytes` as storage" +authors = ["Nikolay Kim ", "Rob Ede "] +keywords = ["string", "bytes", "utf8", "web", "bytestring"] categories = ["no-std", "web-programming"] homepage = "https://actix.rs" -repository = "https://github.com/actix/actix-net.git" +repository = "https://github.com/actix/actix-net" license = "MIT OR Apache-2.0" -edition = "2018" +edition.workspace = true +rust-version.workspace = true -[lib] -name = "bytestring" -path = "src/lib.rs" +[package.metadata.cargo_check_external_types] +allowed_external_types = ["bytes::*", "serde_core::*"] + +[features] +serde = ["dep:serde_core"] [dependencies] -bytes = "1" -serde = { version = "1.0", optional = true } +bytes = { version = "1.2", default-features = false } +serde_core = { version = "1", optional = true } [dev-dependencies] -ahash = { version = "0.7.6", default-features = false } -serde_json = "1.0" +ahash = { version = "0.8", default-features = false } +serde_json = "1" static_assertions = "1.1" -rustversion = "1" + +[lints] +workspace = true diff --git a/bytestring/README.md b/bytestring/README.md new file mode 100644 index 00000000..f55bbc4a --- /dev/null +++ b/bytestring/README.md @@ -0,0 +1,16 @@ +# `bytestring` + +> A UTF-8 encoded read-only string using `Bytes` as storage. + + + +[![crates.io](https://img.shields.io/crates/v/bytestring?label=latest)](https://crates.io/crates/bytestring) +[![Documentation](https://docs.rs/bytestring/badge.svg?version=1.5.0)](https://docs.rs/bytestring/1.5.0) +[![Version](https://img.shields.io/badge/rustc-1.88+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/bytestring.svg) +
+[![Dependency Status](https://deps.rs/crate/bytestring/1.5.0/status.svg)](https://deps.rs/crate/bytestring/1.5.0) +![Download](https://img.shields.io/crates/d/bytestring.svg) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) + + diff --git a/bytestring/src/lib.rs b/bytestring/src/lib.rs index 7ab5a291..10f5cc4b 100644 --- a/bytestring/src/lib.rs +++ b/bytestring/src/lib.rs @@ -1,17 +1,21 @@ -//! A UTF-8 encoded read-only string using Bytes as storage. +//! A UTF-8 encoded read-only string using `Bytes` as storage. +//! +//! See docs for [`ByteString`]. #![no_std] -#![deny(rust_2018_idioms, nonstandard_style)] -#![warn(future_incompatible, missing_docs)] extern crate alloc; -use alloc::{string::String, vec::Vec}; -use core::{borrow, convert::TryFrom, fmt, hash, ops, str}; +use alloc::{ + boxed::Box, + string::{String, ToString}, + vec::Vec, +}; +use core::{borrow::Borrow, fmt, hash, ops, str}; use bytes::Bytes; -/// An immutable UTF-8 encoded string with [`Bytes`] as a storage. +/// An immutable UTF-8 encoded string using [`Bytes`] as the storage. #[derive(Clone, Default, Eq, PartialOrd, Ord)] pub struct ByteString(Bytes); @@ -46,6 +50,66 @@ impl ByteString { pub const unsafe fn from_bytes_unchecked(src: Bytes) -> ByteString { Self(src) } + + /// Divides one bytestring into two at an index, returning both parts. + /// + /// # Panics + /// + /// Panics if `mid` is not on a UTF-8 code point boundary, or if it is past the end of the last + /// code point of the bytestring. + pub fn split_at(&self, mid: usize) -> (ByteString, ByteString) { + let this: &str = self.as_ref(); + let _valid_midpoint_check = this.split_at(mid); + + let mut bytes = self.0.clone(); + let first = bytes.split_to(mid); + let last = bytes; + + unsafe { + ( + ByteString::from_bytes_unchecked(first), + ByteString::from_bytes_unchecked(last), + ) + } + } + + /// Returns a new `ByteString` that is equivalent to the given `subset`. + /// + /// When processing a `ByteString` buffer with other tools, one often gets a `&str` which is in + /// fact a slice of the original `ByteString`; i.e., a subset of it. This function turns that + /// `&str` into another `ByteString`, as if one had sliced the `ByteString` with the offsets + /// that correspond to `subset`. + /// + /// Corresponds to [`Bytes::slice_ref`]. + /// + /// This operation is `O(1)`. + /// + /// # Panics + /// + /// Panics if `subset` is not a sub-slice of this byte string. + /// + /// Note that strings which are only subsets from an equality perspective do not uphold this + /// requirement; see examples. + /// + /// # Examples + /// + /// ``` + /// # use bytestring::ByteString; + /// let string = ByteString::from_static(" foo "); + /// let subset = string.trim(); + /// let substring = string.slice_ref(subset); + /// assert_eq!(substring, "foo"); + /// ``` + /// + /// ```should_panic + /// # use bytestring::ByteString; + /// // panics because the given slice is not derived from the original byte string, despite + /// // being a logical subset of the string + /// ByteString::from_static("foo bar").slice_ref("foo"); + /// ``` + pub fn slice_ref(&self, subset: &str) -> Self { + Self(self.0.slice_ref(subset.as_bytes())) + } } impl PartialEq for ByteString { @@ -60,6 +124,12 @@ impl> PartialEq for ByteString { } } +impl AsRef for ByteString { + fn as_ref(&self) -> &ByteString { + self + } +} + impl AsRef<[u8]> for ByteString { fn as_ref(&self) -> &[u8] { self.0.as_ref() @@ -68,7 +138,7 @@ impl AsRef<[u8]> for ByteString { impl AsRef for ByteString { fn as_ref(&self) -> &str { - &*self + self } } @@ -84,15 +154,14 @@ impl ops::Deref for ByteString { #[inline] fn deref(&self) -> &str { let bytes = self.0.as_ref(); - // SAFETY: - // UTF-8 validity is guaranteed at during construction. + // SAFETY: UTF-8 validity is guaranteed during construction. unsafe { str::from_utf8_unchecked(bytes) } } } -impl borrow::Borrow for ByteString { +impl Borrow for ByteString { fn borrow(&self) -> &str { - &*self + self } } @@ -110,6 +179,20 @@ impl From<&str> for ByteString { } } +impl From> for ByteString { + #[inline] + fn from(value: Box) -> Self { + Self(Bytes::from(value.into_boxed_bytes())) + } +} + +impl From for String { + #[inline] + fn from(value: ByteString) -> Self { + value.to_string() + } +} + impl TryFrom<&[u8]> for ByteString { type Error = str::Utf8Error; @@ -192,8 +275,10 @@ impl fmt::Display for ByteString { mod serde { use alloc::string::String; - use serde::de::{Deserialize, Deserializer}; - use serde::ser::{Serialize, Serializer}; + use serde_core::{ + de::{Deserialize, Deserializer}, + ser::{Serialize, Serializer}, + }; use super::ByteString; @@ -219,19 +304,22 @@ mod serde { #[cfg(test)] mod serde_impl_tests { - use super::*; - - use serde::de::DeserializeOwned; + use serde_core::de::DeserializeOwned; use static_assertions::assert_impl_all; + use super::*; + assert_impl_all!(ByteString: Serialize, DeserializeOwned); } } #[cfg(test)] mod test { - use alloc::borrow::ToOwned; - use core::hash::{Hash, Hasher}; + use alloc::{borrow::ToOwned, format, vec}; + use core::{ + hash::{Hash, Hasher}, + panic::{RefUnwindSafe, UnwindSafe}, + }; use ahash::AHasher; use static_assertions::assert_impl_all; @@ -241,19 +329,10 @@ mod test { assert_impl_all!(ByteString: Send, Sync, Unpin, Sized); assert_impl_all!(ByteString: Clone, Default, Eq, PartialOrd, Ord); assert_impl_all!(ByteString: fmt::Debug, fmt::Display); - - #[rustversion::since(1.56)] - mod above_1_56_impls { - // `[Ref]UnwindSafe` traits were only in std until rust 1.56 - - use core::panic::{RefUnwindSafe, UnwindSafe}; - - use super::*; - assert_impl_all!(ByteString: UnwindSafe, RefUnwindSafe); - } + assert_impl_all!(ByteString: UnwindSafe, RefUnwindSafe); #[test] - fn test_partial_eq() { + fn eq() { let s: ByteString = ByteString::from_static("test"); assert_eq!(s, "test"); assert_eq!(s, *"test"); @@ -261,12 +340,45 @@ mod test { } #[test] - fn test_new() { + fn new() { let _: ByteString = ByteString::new(); } #[test] - fn test_hash() { + fn as_bytes() { + let buf = ByteString::new(); + assert!(buf.as_bytes().is_empty()); + + let buf = ByteString::from("hello"); + assert_eq!(buf.as_bytes(), "hello"); + } + + #[test] + fn from_bytes_unchecked() { + let buf = unsafe { ByteString::from_bytes_unchecked(Bytes::new()) }; + assert!(buf.is_empty()); + + let buf = unsafe { ByteString::from_bytes_unchecked(Bytes::from("hello")) }; + assert_eq!(buf, "hello"); + } + + #[test] + fn as_ref() { + let buf = ByteString::new(); + + let _: &ByteString = buf.as_ref(); + let _: &[u8] = buf.as_ref(); + } + + #[test] + fn borrow() { + let buf = ByteString::new(); + + let _: &str = buf.borrow(); + } + + #[test] + fn hash() { let mut hasher1 = AHasher::default(); "str".hash(&mut hasher1); @@ -277,7 +389,7 @@ mod test { } #[test] - fn test_from_string() { + fn from_string() { let s: ByteString = "hello".to_owned().into(); assert_eq!(&s, "hello"); let t: &str = s.as_ref(); @@ -285,23 +397,30 @@ mod test { } #[test] - fn test_from_str() { + fn from_str() { let _: ByteString = "str".into(); + let _: ByteString = "str".to_owned().into_boxed_str().into(); } #[test] - fn test_from_static_str() { + fn to_string() { + let buf = ByteString::from("foo"); + assert_eq!(String::from(buf), "foo"); + } + + #[test] + fn from_static_str() { static _S: ByteString = ByteString::from_static("hello"); let _ = ByteString::from_static("str"); } #[test] - fn test_try_from_slice() { + fn try_from_slice() { let _ = ByteString::try_from(b"nice bytes").unwrap(); } #[test] - fn test_try_from_array() { + fn try_from_array() { assert_eq!( ByteString::try_from([b'h', b'i']).unwrap(), ByteString::from_static("hi") @@ -309,26 +428,90 @@ mod test { } #[test] - fn test_try_from_bytes() { + fn try_from_vec() { + let _ = ByteString::try_from(vec![b'f', b'o', b'o']).unwrap(); + ByteString::try_from(vec![0, 159, 146, 150]).unwrap_err(); + } + + #[test] + fn try_from_bytes() { let _ = ByteString::try_from(Bytes::from_static(b"nice bytes")).unwrap(); } #[test] - fn test_try_from_bytes_mut() { + fn try_from_bytes_mut() { let _ = ByteString::try_from(bytes::BytesMut::from(&b"nice bytes"[..])).unwrap(); } + #[test] + fn display() { + let buf = ByteString::from("bar"); + assert_eq!(format!("{buf}"), "bar"); + } + + #[test] + fn debug() { + let buf = ByteString::from("baz"); + assert_eq!(format!("{buf:?}"), r#""baz""#); + } + #[cfg(feature = "serde")] #[test] - fn test_serialize() { + fn serialize() { let s: ByteString = serde_json::from_str(r#""nice bytes""#).unwrap(); assert_eq!(s, "nice bytes"); } #[cfg(feature = "serde")] #[test] - fn test_deserialize() { + fn deserialize() { let s = serde_json::to_string(&ByteString::from_static("nice bytes")).unwrap(); assert_eq!(s, r#""nice bytes""#); } + + #[test] + fn slice_ref() { + let string = ByteString::from_static(" foo "); + let subset = string.trim(); + // subset is derived from original byte string + let substring = string.slice_ref(subset); + assert_eq!(substring, "foo"); + } + + #[test] + #[should_panic] + fn slice_ref_catches_not_a_subset() { + // panics because the given slice is not derived from the original byte string, despite + // being a logical subset of the string + ByteString::from_static("foo bar").slice_ref("foo"); + } + + #[test] + fn split_at() { + let buf = ByteString::from_static("foo bar"); + + let (first, last) = buf.split_at(0); + assert_eq!(ByteString::from_static(""), first); + assert_eq!(ByteString::from_static("foo bar"), last); + + let (first, last) = buf.split_at(4); + assert_eq!(ByteString::from_static("foo "), first); + assert_eq!(ByteString::from_static("bar"), last); + + let (first, last) = buf.split_at(7); + assert_eq!(ByteString::from_static("foo bar"), first); + assert_eq!(ByteString::from_static(""), last); + } + + #[test] + #[should_panic = "byte index 1 is not a char boundary;"] + fn split_at_invalid_code_point() { + ByteString::from_static("ยต").split_at(1); + } + + #[test] + #[should_panic = "byte index 9 is out of bounds"] + fn split_at_outside_string() { + ByteString::from_static("foo").split_at(9); + } } diff --git a/clippy.toml b/clippy.toml deleted file mode 100644 index f691ea3d..00000000 --- a/clippy.toml +++ /dev/null @@ -1 +0,0 @@ -msrv = "1.48" diff --git a/codecov.yml b/codecov.yml index 10da425c..18ee3b20 100644 --- a/codecov.yml +++ b/codecov.yml @@ -9,7 +9,7 @@ coverage: default: threshold: 10% # make CI green -ignore: # ignore codecoverage on following paths +ignore: # ignore code coverage on following paths - "examples" - ".github" - "**/*.md" diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..1a6dff87 --- /dev/null +++ b/flake.lock @@ -0,0 +1,58 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1715865404, + "narHash": "sha256-/GJvTdTpuDjNn84j82cU6bXztE0MSkdnTWClUCRub78=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "8dc45382d5206bd292f9c2768b8058a8fd8311d9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1722651103, + "narHash": "sha256-IRiJA0NVAoyaZeKZluwfb2DoTpBAj+FLI0KfybBeDU0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a633d89c6dc9a2a8aae11813a62d7c58b2c0cc51", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1714640452, + "narHash": "sha256-QBx10+k6JWz6u7VsohfSw8g8hjdBZEf8CFzXH1/1Z94=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/50eb7ecf4cd0a5756d7275c8ba36790e5bd53e33.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/50eb7ecf4cd0a5756d7275c8ba36790e5bd53e33.tar.gz" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..1a53656e --- /dev/null +++ b/flake.nix @@ -0,0 +1,30 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; + flake-parts.url = "github:hercules-ci/flake-parts"; + }; + + outputs = inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; + perSystem = { pkgs, config, inputs', system, lib, ... }: { + formatter = pkgs.nixpkgs-fmt; + + devShells.default = pkgs.mkShell { + packages = [ + config.formatter + pkgs.fd + pkgs.just + pkgs.nodePackages.prettier + pkgs.taplo + pkgs.watchexec + ] ++ lib.optional pkgs.stdenv.isDarwin [ + pkgs.pkgsBuildHost.darwin.apple_sdk.frameworks.Security + pkgs.pkgsBuildHost.darwin.apple_sdk.frameworks.CoreFoundation + pkgs.pkgsBuildHost.darwin.apple_sdk.frameworks.SystemConfiguration + pkgs.pkgsBuildHost.libiconv + ]; + }; + }; + }; +} diff --git a/justfile b/justfile new file mode 100644 index 00000000..8c473132 --- /dev/null +++ b/justfile @@ -0,0 +1,121 @@ +_list: + @just --list + +toolchain := "" + +# Check project. +check: && clippy + just --unstable --fmt --check + # nixpkgs-fmt --check . + fd --hidden --type=file -e=md -e=yml --exec-batch prettier --check + fd --hidden -e=toml --exec-batch taplo format --check + fd --hidden -e=toml --exec-batch taplo lint + cargo +nightly fmt -- --check + +# Format project. +fmt: + just --unstable --fmt + # nixpkgs-fmt . + fd --hidden --type=file -e=md -e=yml --exec-batch prettier --write + fd --type=file --hidden -e=toml --exec-batch taplo format + cargo +nightly fmt + +# Downgrade dependencies necessary to run MSRV checks/tests. +[private] +downgrade-for-msrv: + +msrv := ``` + cargo metadata --format-version=1 \ + | jq -r 'first(.packages[] | select(.source == null and .rust_version)) | .rust_version' \ + | sed -E 's/^1\.([0-9]{2})$/1\.\1\.0/' +``` +msrv_rustup := "+" + msrv +non_linux_all_features_list := ``` + cargo metadata --format-version=1 \ + | jq '.packages[] | select(.source == null) | .features | keys' \ + | jq -r --slurp \ + --arg exclusions "tokio-uring,io-uring" \ + 'add | unique | . - ($exclusions | split(",")) | join(",")' +``` +all_crate_features := if os() == "linux" { "--all-features" } else { "--features='" + non_linux_all_features_list + "'" } + +# Run Clippy over workspace. +clippy: + cargo {{ toolchain }} clippy --workspace --all-targets {{ all_crate_features }} + +# Run Clippy using MSRV. +clippy-msrv: downgrade-for-msrv + @just toolchain={{ msrv_rustup }} clippy + +# Test workspace code. +[macos] +[windows] +test: + cargo {{ toolchain }} test --lib --tests --package=actix-macros + cargo {{ toolchain }} nextest run --no-tests=warn --workspace --exclude=actix-macros --no-default-features + cargo {{ toolchain }} nextest run --no-tests=warn --workspace --exclude=actix-macros {{ all_crate_features }} + +# Test workspace code. +[linux] +test: + cargo {{ toolchain }} test --lib --tests --package=actix-macros + cargo {{ toolchain }} nextest run --no-tests=warn --workspace --exclude=actix-macros --no-default-features + cargo {{ toolchain }} nextest run --no-tests=warn --workspace --exclude=actix-macros {{ non_linux_all_features_list }} + cargo {{ toolchain }} nextest run --no-tests=warn --workspace --exclude=actix-macros {{ all_crate_features }} + +# Test workspace using MSRV. +test-msrv: downgrade-for-msrv + @just toolchain={{ msrv_rustup }} test + +# Test workspace docs. +test-docs: && doc + cargo {{ toolchain }} test --doc --workspace {{ all_crate_features }} --no-fail-fast -- --nocapture + +# Test workspace. +test-all: test test-docs + +# Document crates in workspace. +doc *args: && doc-set-workspace-crates + rm -f "$(cargo metadata --format-version=1 | jq -r '.target_directory')/doc/crates.js" + RUSTDOCFLAGS="--cfg=docsrs -Dwarnings" cargo +nightly doc --no-deps --workspace {{ all_crate_features }} {{ args }} + +[private] +doc-set-workspace-crates: + #!/usr/bin/env bash + ( + echo "window.ALL_CRATES =" + cargo metadata --format-version=1 \ + | jq '[.packages[] | select(.source == null) | .targets | map(select(.doc) | .name)] | flatten' + echo ";" + ) > "$(cargo metadata --format-version=1 | jq -r '.target_directory')/doc/crates.js" + +# Document crates in workspace and watch for changes. +doc-watch: + @just doc --open + cargo watch -- just doc + +# Check for unintentional external type exposure on all crates in workspace. +check-external-types-all: + #!/usr/bin/env bash + set -euo pipefail + exit=0 + for f in $(find . -mindepth 2 -maxdepth 2 -name Cargo.toml | grep -vE "\-codegen/|\-derive/|\-macros/"); do + if ! just toolchain="+nightly" check-external-types-manifest "$f"; then exit=1; fi + echo + echo + done + exit $exit + +# Check for unintentional external type exposure on all crates in workspace. +check-external-types-all-table toolchain="+nightly": + #!/usr/bin/env bash + set -euo pipefail + for f in $(find . -mindepth 2 -maxdepth 2 -name Cargo.toml | grep -vE "\-codegen/|\-derive/|\-macros/"); do + echo + echo "Checking for $f" + just toolchain="+nightly" check-external-types-manifest "$f" --output-format=markdown-table + done + +# Check for unintentional external type exposure on a crate. +check-external-types-manifest manifest_path *extra_args="": + cargo {{ toolchain }} check-external-types --manifest-path "{{ manifest_path }}" {{ extra_args }} diff --git a/local-channel/CHANGES.md b/local-channel/CHANGES.md index 7ee9ea23..bdc0c7ea 100644 --- a/local-channel/CHANGES.md +++ b/local-channel/CHANGES.md @@ -1,11 +1,25 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased +- Minimum supported Rust version (MSRV) is now 1.88. + +## 0.1.5 + +- No significant changes since `0.1.4`. + +## 0.1.4 + +- Minimum supported Rust version (MSRV) is now 1.65. + +## 0.1.3 + +- Minimum supported Rust version (MSRV) is now 1.49. + +## 0.1.2 -## 0.1.2 - 2021-04-01 - No significant changes from `0.1.1`. +## 0.1.1 -## 0.1.1 - 2021-03-29 -- Move local mpsc channel to it's own crate. +- Move local MPSC channel to it's own crate. diff --git a/local-channel/Cargo.toml b/local-channel/Cargo.toml index aba58f65..e3ee5b65 100644 --- a/local-channel/Cargo.toml +++ b/local-channel/Cargo.toml @@ -1,21 +1,22 @@ [package] name = "local-channel" -version = "0.1.2" +version = "0.1.5" description = "A non-threadsafe multi-producer, single-consumer, futures-aware, FIFO queue" -authors = [ - "Nikolay Kim ", - "Rob Ede ", -] -repository = "https://github.com/actix/actix-net.git" +authors = ["Nikolay Kim ", "Rob Ede "] +repository = "https://github.com/actix/actix-net" keywords = ["channel", "local", "futures"] -license = "MIT OR Apache-2.0" -edition = "2018" +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[package.metadata.cargo_check_external_types] +allowed_external_types = ["futures_core::*", "futures_sink::*"] [dependencies] -futures-core = { version = "0.3.7", default-features = false } -futures-sink = { version = "0.3.7", default-features = false } -futures-util = { version = "0.3.7", default-features = false } +futures-core = "0.3.17" +futures-sink = "0.3.17" local-waker = "0.1" [dev-dependencies] -tokio = { version = "1.13.1", features = ["rt", "macros"] } +futures-util = { version = "0.3.17", default-features = false } +tokio = { version = "1.44.2", features = ["rt", "macros"] } diff --git a/local-channel/LICENSE-APACHE b/local-channel/LICENSE-APACHE new file mode 120000 index 00000000..965b606f --- /dev/null +++ b/local-channel/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/local-channel/LICENSE-MIT b/local-channel/LICENSE-MIT new file mode 120000 index 00000000..76219eb7 --- /dev/null +++ b/local-channel/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/local-channel/README.md b/local-channel/README.md new file mode 100644 index 00000000..5cc14692 --- /dev/null +++ b/local-channel/README.md @@ -0,0 +1,16 @@ +# `local-channel` + +> A non-threadsafe multi-producer, single-consumer, futures-aware, FIFO queue. + + + +[![crates.io](https://img.shields.io/crates/v/local-channel?label=latest)](https://crates.io/crates/local-channel) +[![Documentation](https://docs.rs/local-channel/badge.svg?version=0.1.5)](https://docs.rs/local-channel/0.1.5) +[![Version](https://img.shields.io/badge/rustc-1.88+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/local-channel.svg) +
+[![Dependency Status](https://deps.rs/crate/local-channel/0.1.5/status.svg)](https://deps.rs/crate/local-channel/0.1.5) +![Download](https://img.shields.io/crates/d/local-channel.svg) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) + + diff --git a/local-channel/src/lib.rs b/local-channel/src/lib.rs index e3f493ad..f89bfdb4 100644 --- a/local-channel/src/lib.rs +++ b/local-channel/src/lib.rs @@ -1,4 +1,6 @@ //! Non-thread-safe channels. +//! +//! See docs for [`mpsc::channel()`]. #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible, missing_docs)] diff --git a/local-channel/src/mpsc.rs b/local-channel/src/mpsc.rs index c75d03bb..7fec5611 100644 --- a/local-channel/src/mpsc.rs +++ b/local-channel/src/mpsc.rs @@ -4,6 +4,7 @@ use alloc::{collections::VecDeque, rc::Rc}; use core::{ cell::RefCell, fmt, + future::poll_fn, pin::Pin, task::{Context, Poll}, }; @@ -11,7 +12,6 @@ use std::error::Error; use futures_core::stream::Stream; use futures_sink::Sink; -use futures_util::future::poll_fn; use local_waker::LocalWaker; /// Creates a unbounded in-memory channel with buffered storage. diff --git a/local-waker/CHANGES.md b/local-waker/CHANGES.md index 0ca00359..00883d58 100644 --- a/local-waker/CHANGES.md +++ b/local-waker/CHANGES.md @@ -1,11 +1,21 @@ # Changes -## Unreleased - 2021-xx-xx +## Unreleased +- Minimum supported Rust version (MSRV) is now 1.88. + +## 0.1.4 + +- Minimum supported Rust version (MSRV) is now 1.65. + +## 0.1.3 + +- Minimum supported Rust version (MSRV) is now 1.49. + +## 0.1.2 -## 0.1.2 - 2021-12-18 - Fix crate metadata. +## 0.1.1 -## 0.1.1 - 2021-03-29 - Move `LocalWaker` to it's own crate. diff --git a/local-waker/Cargo.toml b/local-waker/Cargo.toml index 2d83bcea..772dea55 100644 --- a/local-waker/Cargo.toml +++ b/local-waker/Cargo.toml @@ -1,15 +1,13 @@ [package] name = "local-waker" -version = "0.1.2" +version = "0.1.4" description = "A synchronization primitive for thread-local task wakeup" -authors = [ - "Nikolay Kim ", - "Rob Ede ", -] +authors = ["Nikolay Kim ", "Rob Ede "] +repository = "https://github.com/actix/actix-net" keywords = ["waker", "local", "futures", "no-std"] -repository = "https://github.com/actix/actix-net.git" categories = ["asynchronous", "no-std"] -license = "MIT OR Apache-2.0" -edition = "2018" +license.workspace = true +edition.workspace = true +rust-version.workspace = true [dependencies] diff --git a/local-waker/LICENSE-APACHE b/local-waker/LICENSE-APACHE new file mode 120000 index 00000000..965b606f --- /dev/null +++ b/local-waker/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/local-waker/LICENSE-MIT b/local-waker/LICENSE-MIT new file mode 120000 index 00000000..76219eb7 --- /dev/null +++ b/local-waker/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/local-waker/README.md b/local-waker/README.md new file mode 100644 index 00000000..db9d24d8 --- /dev/null +++ b/local-waker/README.md @@ -0,0 +1,16 @@ +# `local-waker` + +> A synchronization primitive for thread-local task wakeup. + + + +[![crates.io](https://img.shields.io/crates/v/local-waker?label=latest)](https://crates.io/crates/local-waker) +[![Documentation](https://docs.rs/local-waker/badge.svg?version=0.1.4)](https://docs.rs/local-waker/0.1.4) +[![Version](https://img.shields.io/badge/rustc-1.88+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/local-waker.svg) +
+[![Dependency Status](https://deps.rs/crate/local-waker/0.1.4/status.svg)](https://deps.rs/crate/local-waker/0.1.4) +![Download](https://img.shields.io/crates/d/local-waker.svg) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) + + diff --git a/local-waker/src/lib.rs b/local-waker/src/lib.rs index 6a6a5558..b131df84 100644 --- a/local-waker/src/lib.rs +++ b/local-waker/src/lib.rs @@ -6,7 +6,7 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible, missing_docs)] -use core::{cell::Cell, fmt, marker::PhantomData, task::Waker}; +use core::{cell::UnsafeCell, fmt, marker::PhantomData, task::Waker}; /// A synchronization primitive for task wakeup. /// @@ -27,7 +27,7 @@ use core::{cell::Cell, fmt, marker::PhantomData, task::Waker}; /// [`wake`]: LocalWaker::wake #[derive(Default)] pub struct LocalWaker { - pub(crate) waker: Cell>, + pub(crate) waker: UnsafeCell>, // mark LocalWaker as a !Send type. _phantom: PhantomData<*const ()>, } @@ -43,8 +43,21 @@ impl LocalWaker { /// Returns `true` if waker was registered before. #[inline] pub fn register(&self, waker: &Waker) -> bool { - let last_waker = self.waker.replace(Some(waker.clone())); - last_waker.is_some() + let mut registered = false; + + // SAFETY: `LocalWaker` is `!Send`, threfore this cannot be called from a separate thread. + // And this is an unique access before the assignment below. + if let Some(prev) = unsafe { &*self.waker.get() } { + if waker.will_wake(prev) { + return true; + } + registered = true; + } + + // SAFETY: This can cause data races if called from a separate thread, + // but `LocalWaker` is `!Send` + `!Sync` so this won't happen. + unsafe { *self.waker.get() = Some(waker.clone()) } + registered } /// Calls `wake` on the last `Waker` passed to `register`. @@ -62,7 +75,9 @@ impl LocalWaker { /// If a waker has not been registered, this returns `None`. #[inline] pub fn take(&self) -> Option { - self.waker.take() + // SAFETY: This can cause data races if called from a separate thread, + // but `LocalWaker` is `!Send` + `!Sync` so this won't happen. + unsafe { (*self.waker.get()).take() } } } diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 973e002c..00000000 --- a/rustfmt.toml +++ /dev/null @@ -1,2 +0,0 @@ -max_width = 96 -reorder_imports = true diff --git a/scripts/free-disk-space.sh b/scripts/free-disk-space.sh new file mode 100755 index 00000000..2946cfcf --- /dev/null +++ b/scripts/free-disk-space.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The Azure provided machines typically have the following disk allocation: +# Total space: 85GB +# Allocated: 67 GB +# Free: 17 GB +# This script frees up 28 GB of disk space by deleting unneeded packages and +# large directories. +# The Flink end to end tests download and generate more than 17 GB of files, +# causing unpredictable behavior and build failures. + +echo "==============================================================================" +echo "Freeing up disk space on CI system" +echo "==============================================================================" + +echo "Listing 100 largest packages" +dpkg-query -Wf '${Installed-Size}\t${Package}\n' | sort -n | tail -n 100 +df -h + +echo "Removing large packages" +sudo apt-get remove -y '^dotnet-.*' +sudo apt-get remove -y 'php.*' +sudo apt-get remove -y '^mongodb-.*' +sudo apt-get remove -y '^mysql-.*' +sudo apt-get remove -y azure-cli google-cloud-sdk hhvm google-chrome-stable firefox powershell mono-devel libgl1-mesa-dri +sudo apt-get autoremove -y +sudo apt-get clean +df -h + +echo "Removing large directories" +sudo rm -rf /usr/share/dotnet/ +sudo rm -rf /usr/local/graalvm/ +sudo rm -rf /usr/local/.ghcup/ +sudo rm -rf /usr/local/share/powershell +sudo rm -rf /usr/local/share/chromium +sudo rm -rf /usr/local/lib/android +sudo rm -rf /usr/local/lib/node_modules +df -h