1
0
mirror of https://github.com/fafhrd91/actix-net synced 2025-08-15 02:10:41 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Rob Ede
8666c4063f temp 2020-12-23 19:44:54 +00:00
155 changed files with 9154 additions and 7191 deletions

29
.github/workflows/bench.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Benchmark (Linux)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
check_benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true
- name: Check benchmark
uses: actions-rs/cargo@v1
with:
command: bench
args: --package=actix-service

View File

@@ -1,131 +0,0 @@
name: CI
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches: [master]
jobs:
build_and_test:
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:
- 1.46.0 # MSRV
- stable
- 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 minimal
uses: actions-rs/cargo@v1
with:
command: hack
args: check --workspace --no-default-features
- name: check minimal + tests
uses: actions-rs/cargo@v1
with:
command: hack
args: check --workspace --no-default-features --tests --examples
- name: check default
uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --tests --examples
- name: check full
# TODO: compile OpenSSL and run tests on MinGW
if: matrix.target.triple != 'x86_64-pc-windows-gnu'
uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --all-features --tests --examples
- name: tests
if: matrix.target.triple != 'x86_64-pc-windows-gnu'
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --all-features --no-fail-fast -- --nocapture
- name: Generate coverage file
if: >
matrix.target.os == 'ubuntu-latest'
&& matrix.version == 'stable'
&& github.ref == 'refs/heads/master'
run: |
cargo install cargo-tarpaulin
cargo tarpaulin --out Xml --verbose
- name: Upload to Codecov
if: >
matrix.target.os == 'ubuntu-latest'
&& matrix.version == 'stable'
&& github.ref == 'refs/heads/master'
uses: codecov/codecov-action@v1
with:
file: cobertura.xml
- name: Clear the cargo caches
run: |
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo-cache

View File

@@ -1,42 +1,34 @@
name: Lint
on: on:
pull_request: pull_request:
types: [opened, synchronize, reopened] types: [opened, synchronize, reopened]
name: Clippy and rustfmt Check
jobs: jobs:
fmt: clippy_check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Rust - uses: actions-rs/toolchain@v1
uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: stable
profile: minimal
components: rustfmt components: rustfmt
profile: minimal
override: true override: true
- name: Rustfmt Check - name: Check with rustfmt
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: fmt command: fmt
args: --all -- --check args: --all -- --check
clippy: - uses: actions-rs/toolchain@v1
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: nightly
profile: minimal
components: clippy components: clippy
profile: minimal
override: true override: true
- name: Clippy Check - name: Check with Clippy
uses: actions-rs/clippy-check@v1 uses: actions-rs/clippy-check@v1
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace --tests --all-features args: --workspace --tests

82
.github/workflows/linux.yml vendored Normal file
View File

@@ -0,0 +1,82 @@
name: CI (Linux)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- 1.42.0
- stable
- nightly
name: ${{ matrix.version }} - x86_64-unknown-linux-gnu
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Cache cargo dirs
uses: actions/cache@v2
with:
path:
~/.cargo/registry
~/.cargo/git
~/.cargo/bin
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v2
with:
path: target
key: ${{ matrix.version }}-x86_64-unknown-linux-gnu-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
timeout-minutes: 40
with:
command: test
args: --workspace --exclude=actix-tls --no-fail-fast -- --nocapture
- name: Generate coverage file
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
run: |
cargo install cargo-tarpaulin
cargo tarpaulin --out Xml --workspace
- name: Upload to Codecov
if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
uses: codecov/codecov-action@v1
with:
file: cobertura.xml
- name: Clear the cargo caches
run: |
rustup update stable
rustup override set stable
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo-cache

43
.github/workflows/macos.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: CI (macOS)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
name: ${{ matrix.version }} - x86_64-apple-darwin
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-apple-darwin
profile: minimal
override: true
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --exclude=actix-tls --no-fail-fast -- --nocapture

View File

@@ -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 '<meta http-equiv="refresh" content="0;url=actix_server/index.html">' > 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

45
.github/workflows/windows-mingw.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: CI (Windows-mingw)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
name: ${{ matrix.version }} - x86_64-pc-windows-gnu
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-x86_64-pc-windows-gnu
profile: minimal
override: true
- name: Install MSYS2
uses: msys2/setup-msys2@v2
- name: Install packages
run: |
msys2 -c 'pacman -Sy --noconfirm pacman'
msys2 -c 'pacman --noconfirm -S base-devel pkg-config'
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --bins --examples --tests

69
.github/workflows/windows.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: CI (Windows)
on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- master
- '1.0'
env:
VCPKGRS_DYNAMIC: 1
jobs:
build_and_test:
strategy:
fail-fast: false
matrix:
version:
- stable
- nightly
target:
- x86_64-pc-windows-msvc
- i686-pc-windows-msvc
name: ${{ matrix.version }} - ${{ matrix.target }}
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Install ${{ matrix.version }}
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.version }}-${{ matrix.target }}
profile: minimal
override: true
- name: Install OpenSSL (x64)
if: matrix.target == 'x86_64-pc-windows-msvc'
run: |
vcpkg integrate install
vcpkg install openssl:x64-windows
Get-ChildItem C:\vcpkg\installed\x64-windows\bin
Get-ChildItem C:\vcpkg\installed\x64-windows\lib
Copy-Item C:\vcpkg\installed\x64-windows\bin\libcrypto-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libcrypto.dll
Copy-Item C:\vcpkg\installed\x64-windows\bin\libssl-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libssl.dll
- name: Install OpenSSL (x86)
if: matrix.target == 'i686-pc-windows-msvc'
run: |
vcpkg integrate install
vcpkg install openssl:x86-windows
Get-ChildItem C:\vcpkg\installed\x86-windows\bin
Get-ChildItem C:\vcpkg\installed\x86-windows\lib
Copy-Item C:\vcpkg\installed\x86-windows\bin\libcrypto-1_1.dll C:\vcpkg\installed\x86-windows\bin\libcrypto.dll
Copy-Item C:\vcpkg\installed\x86-windows\bin\libssl-1_1.dll C:\vcpkg\installed\x86-windows\bin\libssl.dll
- name: check build
uses: actions-rs/cargo@v1
with:
command: check
args: --workspace --bins --examples --tests
- name: tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --exclude=actix-tls --no-fail-fast -- --nocapture

View File

@@ -1,25 +1,31 @@
[workspace] [workspace]
members = [ members = [
"actix-codec", "actix-codec",
"actix-macros", "actix-connect",
"actix-router",
"actix-rt", "actix-rt",
"actix-server", "actix-macros",
"actix-service", "actix-service",
"actix-server",
"actix-testing",
"actix-threadpool",
"actix-tls", "actix-tls",
"actix-tracing", "actix-tracing",
"actix-utils", "actix-utils",
"bytestring", "router",
"string",
] ]
[patch.crates-io] [patch.crates-io]
actix-codec = { path = "actix-codec" } actix-codec = { path = "actix-codec" }
actix-macros = { path = "actix-macros" } actix-connect = { path = "actix-connect" }
actix-router = { path = "actix-router" }
actix-rt = { path = "actix-rt" } actix-rt = { path = "actix-rt" }
actix-macros = { path = "actix-macros" }
actix-server = { path = "actix-server" } actix-server = { path = "actix-server" }
actix-service = { path = "actix-service" } actix-service = { path = "actix-service" }
actix-testing = { path = "actix-testing" }
actix-threadpool = { path = "actix-threadpool" }
actix-tls = { path = "actix-tls" } actix-tls = { path = "actix-tls" }
actix-tracing = { path = "actix-tracing" } actix-tracing = { path = "actix-tracing" }
actix-utils = { path = "actix-utils" } actix-utils = { path = "actix-utils" }
bytestring = { path = "bytestring" } actix-router = { path = "router" }
bytestring = { path = "string" }

View File

@@ -1,12 +1,9 @@
# Actix Net # Actix net [![codecov](https://codecov.io/gh/actix/actix-net/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-net) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
> A collection of lower-level libraries for composable network services. Actix net - framework for composable network services
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-server)
[![codecov](https://codecov.io/gh/actix/actix-net/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-net)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Build statuses ## Build statuses
| Platform | Build Status | | Platform | Build Status |
| ---------------- | ------------ | | ---------------- | ------------ |
| Linux | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Linux)") | | Linux | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Linux)") |
@@ -14,13 +11,59 @@
| Windows | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Windows%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows)") | | Windows | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Windows%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows)") |
| Windows (MinGW) | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Windows-mingw%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows-mingw)") | | Windows (MinGW) | [![build status](https://github.com/actix/actix-net/workflows/CI%20%28Windows-mingw%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-net/actions?query=workflow%3A"CI+(Windows-mingw)") |
## Example ## Documentation & community resources
See `actix-server/examples` and `actix-tls/examples` for some basic examples.
### MSRV * [Chat on Gitter](https://gitter.im/actix/actix)
This repo's Minimum Supported Rust Version (MSRV) is 1.46.0. * Minimum supported Rust version: 1.42 or later
## Example
```rust
fn main() -> io::Result<()> {
// load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder.set_private_key_file("./examples/key.pem", SslFiletype::PEM).unwrap();
builder.set_certificate_chain_file("./examples/cert.pem").unwrap();
let acceptor = builder.build();
let num = Arc::new(AtomicUsize::new(0));
// bind socket address and start workers. By default server uses number of
// available logical cpu as threads count. actix net start separate
// instances of service pipeline in each worker.
Server::build()
.bind(
// configure service pipeline
"basic", "0.0.0.0:8443",
move || {
let num = num.clone();
let acceptor = acceptor.clone();
// construct transformation pipeline
pipeline(
// service for converting incoming TcpStream to a SslStream<TcpStream>
fn_service(move |stream: actix_rt::net::TcpStream| async move {
SslAcceptorExt::accept_async(&acceptor, stream.into_parts().0).await
.map_err(|e| println!("Openssl error: {}", e))
}))
// .and_then() combinator chains result of previos service call to argument
/// for next service calll. in this case, on success we chain
/// ssl stream to the `logger` service.
.and_then(fn_service(logger))
// Next service counts number of connections
.and_then(move |_| {
let num = num.fetch_add(1, Ordering::Relaxed);
println!("got ssl connection {:?}", num);
future::ok(())
})
},
)?
.run()
}
```
## License ## License
This project is licensed under either of This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
@@ -30,5 +73,6 @@ at your option.
## Code of Conduct ## Code of Conduct
Contribution to the actix-net repo is organized under the terms of the Contributor Covenant. Contribution to the actix-net crate is organized under the terms of the
The Actix team promises to intervene to uphold that code of conduct. Contributor Covenant, the maintainer of actix-net, @fafhrd91, promises to
intervene to uphold that code of conduct.

View File

@@ -1,25 +1,14 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2020-xx-xx
* Upgrade `pin-project` to `1.0`.
## 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]
[#237]: https://github.com/actix/actix-net/pull/237
## 0.3.0 - 2020-08-23 ## 0.3.0 - 2020-08-23
* No changes from beta 2. * No changes from beta 2.
## 0.3.0-beta.2 - 2020-08-19 ## 0.3.0-beta.2 - 2020-08-19
* Remove unused type parameter from `Framed::replace_codec`. * Remove unused type parameter from `Framed::replace_codec`.
## 0.3.0-beta.1 - 2020-08-19 ## 0.3.0-beta.1 - 2020-08-19
* Use `.advance()` instead of `.split_to()`. * Use `.advance()` instead of `.split_to()`.
* Upgrade `tokio-util` to `0.3`. * Upgrade `tokio-util` to `0.3`.
@@ -29,31 +18,32 @@
* Add method on `Framed` to get a pinned reference to the underlying I/O. * Add method on `Framed` to get a pinned reference to the underlying I/O.
* Add method on `Framed` check emptiness of read buffer. * Add method on `Framed` check emptiness of read buffer.
## [0.2.0] - 2019-12-10
## 0.2.0 - 2019-12-10
* Use specific futures dependencies * Use specific futures dependencies
## [0.2.0-alpha.4]
## 0.2.0-alpha.4
* Fix buffer remaining capacity calculation * Fix buffer remaining capacity calculation
## [0.2.0-alpha.3]
## 0.2.0-alpha.3
* Use tokio 0.2 * Use tokio 0.2
* Fix low/high watermark for write/read buffers * Fix low/high watermark for write/read buffers
## [0.2.0-alpha.2]
## 0.2.0-alpha.2
* Migrated to `std::future` * Migrated to `std::future`
## [0.1.2] - 2019-03-27
## 0.1.2 - 2019-03-27
* Added `Framed::map_io()` method. * Added `Framed::map_io()` method.
## [0.1.1] - 2019-03-06
## 0.1.1 - 2019-03-06
* Added `FramedParts::with_read_buffer()` method. * Added `FramedParts::with_read_buffer()` method.
## [0.1.0] - 2018-12-09
## 0.1.0 - 2018-12-09
* Move codec to separate crate * Move codec to separate crate

View File

@@ -1,12 +1,12 @@
[package] [package]
name = "actix-codec" name = "actix-codec"
version = "0.4.0-beta.1" version = "0.3.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Codec utilities for working with framed protocols" description = "Codec utilities for working with framed protocols."
keywords = ["network", "framework", "async", "futures"] keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-codec" documentation = "https://docs.rs/actix-codec/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
@@ -17,10 +17,10 @@ path = "src/lib.rs"
[dependencies] [dependencies]
bitflags = "1.2.1" bitflags = "1.2.1"
bytes = "1" bytes = "0.5.2"
futures-core = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.4", default-features = false }
futures-sink = { version = "0.3.7", default-features = false } futures-sink = { version = "0.3.4", default-features = false }
log = "0.4" log = "0.4"
pin-project-lite = "0.2" pin-project = "1.0.0"
tokio = "1" tokio = { version = "0.2.5", default-features = false }
tokio-util = { version = "0.6", features = ["codec", "io"] } tokio-util = { version = "0.3.1", default-features = false, features = ["codec"] }

View File

@@ -14,7 +14,7 @@ impl Encoder<Bytes> for BytesCodec {
#[inline] #[inline]
fn encode(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> { fn encode(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> {
dst.extend_from_slice(item.chunk()); dst.extend_from_slice(item.bytes());
Ok(()) Ok(())
} }
} }

View File

@@ -5,6 +5,7 @@ use std::{fmt, io};
use bytes::{Buf, BytesMut}; use bytes::{Buf, BytesMut};
use futures_core::{ready, Stream}; use futures_core::{ready, Stream};
use futures_sink::Sink; use futures_sink::Sink;
use pin_project::pin_project;
use crate::{AsyncRead, AsyncWrite, Decoder, Encoder}; use crate::{AsyncRead, AsyncWrite, Decoder, Encoder};
@@ -20,23 +21,22 @@ bitflags::bitflags! {
} }
} }
pin_project_lite::pin_project! { /// A unified `Stream` and `Sink` interface to an underlying I/O object, using
/// A unified `Stream` and `Sink` interface to an underlying I/O object, using /// the `Encoder` and `Decoder` traits to encode and decode frames.
/// the `Encoder` and `Decoder` traits to encode and decode frames. ///
/// /// Raw I/O objects work with byte sequences, but higher-level code usually
/// Raw I/O objects work with byte sequences, but higher-level code usually /// wants to batch these into meaningful chunks, called "frames". This
/// wants to batch these into meaningful chunks, called "frames". This /// method layers framing on top of an I/O object, by using the `Encoder`/`Decoder`
/// method layers framing on top of an I/O object, by using the `Encoder`/`Decoder` /// traits to handle encoding and decoding of message frames. Note that
/// traits to handle encoding and decoding of message frames. Note that /// the incoming and outgoing frame types may be distinct.
/// the incoming and outgoing frame types may be distinct. #[pin_project]
pub struct Framed<T, U> { pub struct Framed<T, U> {
#[pin] #[pin]
io: T, io: T,
codec: U, codec: U,
flags: Flags, flags: Flags,
read_buf: BytesMut, read_buf: BytesMut,
write_buf: BytesMut, write_buf: BytesMut,
}
} }
impl<T, U> Framed<T, U> impl<T, U> Framed<T, U>
@@ -220,8 +220,7 @@ impl<T, U> Framed<T, U> {
if remaining < LW { if remaining < LW {
this.read_buf.reserve(HW - remaining) this.read_buf.reserve(HW - remaining)
} }
let cnt = match this.io.poll_read_buf(cx, &mut this.read_buf) {
let cnt = match tokio_util::io::poll_read_buf(this.io, cx, this.read_buf) {
Poll::Pending => return Poll::Pending, Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))), Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))),
Poll::Ready(Ok(cnt)) => cnt, Poll::Ready(Ok(cnt)) => cnt,

View File

@@ -1,4 +1,4 @@
//! Codec utilities for working with framed protocols. //! Utilities for encoding and decoding frames.
//! //!
//! Contains adapters to go from streams of bytes, [`AsyncRead`] and //! Contains adapters to go from streams of bytes, [`AsyncRead`] and
//! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`]. //! [`AsyncWrite`], to framed streams implementing [`Sink`] and [`Stream`].
@@ -18,6 +18,5 @@ mod framed;
pub use self::bcodec::BytesCodec; pub use self::bcodec::BytesCodec;
pub use self::framed::{Framed, FramedParts}; pub use self::framed::{Framed, FramedParts};
pub use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; pub use tokio::io::{AsyncRead, AsyncWrite};
pub use tokio_util::codec::{Decoder, Encoder}; pub use tokio_util::codec::{Decoder, Encoder};
pub use tokio_util::io::poll_read_buf;

154
actix-connect/CHANGES.md Normal file
View File

@@ -0,0 +1,154 @@
# Changes
## Unreleased - 2020-xx-xx
## 2.0.0 - 2020-09-02
- No significant changes from `2.0.0-alpha.4`.
## 2.0.0-alpha.4 - 2020-08-17
### Changed
* Update `rustls` dependency to 0.18
* Update `tokio-rustls` dependency to 0.14
## [2.0.0-alpha.3] - 2020-05-08
### Fixed
* Corrected spelling of `ConnectError::Unresolverd` to `ConnectError::Unresolved`
## [2.0.0-alpha.2] - 2020-03-08
### Changed
* Update `trust-dns-proto` dependency to 0.19. [#116]
* Update `trust-dns-resolver` dependency to 0.19. [#116]
* `Address` trait is now required to have static lifetime. [#116]
* `start_resolver` and `start_default_resolver` are now `async` and may return a `ConnectError`. [#116]
[#116]: https://github.com/actix/actix-net/pull/116
## [2.0.0-alpha.1] - 2020-03-03
### Changed
* Update `rustls` dependency to 0.17
* Update `tokio-rustls` dependency to 0.13
## [1.0.2] - 2020-01-15
* Fix actix-service 1.0.3 compatibility
## [1.0.1] - 2019-12-15
* Fix trust-dns-resolver compilation
## [1.0.0] - 2019-12-11
* Release
## [1.0.0-alpha.3] - 2019-12-07
### Changed
* Migrate to tokio 0.2
## [1.0.0-alpha.2] - 2019-12-02
### Changed
* Migrated to `std::future`
## [0.3.0] - 2019-10-03
### Changed
* Update `rustls` to 0.16
* Minimum required Rust version upped to 1.37.0
## [0.2.5] - 2019-09-05
* Add `TcpConnectService`
## [0.2.4] - 2019-09-02
* Use arbiter's storage for default async resolver
## [0.2.3] - 2019-08-05
* Add `ConnectService` and `OpensslConnectService`
## [0.2.2] - 2019-07-24
* Add `rustls` support
## [0.2.1] - 2019-07-17
### Added
* Expose Connect addrs #30
### Changed
* Update `derive_more` to 0.15
## [0.2.0] - 2019-05-12
### Changed
* Upgrade to actix-service 0.4
## [0.1.5] - 2019-04-19
### Added
* `Connect::set_addr()`
### Changed
* Use trust-dns-resolver 0.11.0
## [0.1.4] - 2019-04-12
### Changed
* Do not start default resolver immediately for default connector.
## [0.1.3] - 2019-04-11
### Changed
* Start trust-dns default resolver on first use
## [0.1.2] - 2019-04-04
### Added
* Log error if dns system config could not be loaded.
### Changed
* Rename connect Connector to TcpConnector #10
## [0.1.1] - 2019-03-15
### Fixed
* Fix error handling for single address
## [0.1.0] - 2019-03-14
* Refactor resolver and connector services
* Rename crate

58
actix-connect/Cargo.toml Normal file
View File

@@ -0,0 +1,58 @@
[package]
name = "actix-connect"
version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "TCP connector service for Actix ecosystem."
keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-connect/"
categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0"
edition = "2018"
[package.metadata.docs.rs]
features = ["openssl", "rustls", "uri"]
[lib]
name = "actix_connect"
path = "src/lib.rs"
[features]
default = ["uri"]
# openssl
openssl = ["open-ssl", "tokio-openssl"]
# rustls
rustls = ["rust-tls", "tokio-rustls", "webpki"]
# support http::Uri as connect address
uri = ["http"]
[dependencies]
actix-service = "1.0.6"
actix-codec = "0.3.0"
actix-utils = "2.0.0"
actix-rt = "1.1.1"
derive_more = "0.99.2"
either = "1.5.3"
futures-util = { version = "0.3.4", default-features = false }
http = { version = "0.2.0", optional = true }
log = "0.4"
trust-dns-proto = { version = "0.19", default-features = false, features = ["tokio-runtime"] }
trust-dns-resolver = { version = "0.19", default-features = false, features = ["tokio-runtime", "system-config"] }
# openssl
open-ssl = { package = "openssl", version = "0.10", optional = true }
tokio-openssl = { version = "0.4.0", optional = true }
# rustls
rust-tls = { package = "rustls", version = "0.18.0", optional = true }
tokio-rustls = { version = "0.14.0", optional = true }
webpki = { version = "0.21", optional = true }
[dev-dependencies]
bytes = "0.5.3"
actix-testing = "1.0.0"

View File

@@ -0,0 +1,282 @@
use std::collections::{vec_deque, VecDeque};
use std::fmt;
use std::iter::{FromIterator, FusedIterator};
use std::net::SocketAddr;
use either::Either;
/// Connect request
pub trait Address: Unpin + 'static {
/// Host name of the request
fn host(&self) -> &str;
/// Port of the request
fn port(&self) -> Option<u16>;
}
impl Address for String {
fn host(&self) -> &str {
&self
}
fn port(&self) -> Option<u16> {
None
}
}
impl Address for &'static str {
fn host(&self) -> &str {
self
}
fn port(&self) -> Option<u16> {
None
}
}
/// Connect request
#[derive(Eq, PartialEq, Debug, Hash)]
pub struct Connect<T> {
pub(crate) req: T,
pub(crate) port: u16,
pub(crate) addr: Option<Either<SocketAddr, VecDeque<SocketAddr>>>,
}
impl<T: Address> Connect<T> {
/// Create `Connect` instance by splitting the string by ':' and convert the second part to u16
pub fn new(req: T) -> Connect<T> {
let (_, port) = parse(req.host());
Connect {
req,
port: port.unwrap_or(0),
addr: None,
}
}
/// Create new `Connect` instance from host and address. Connector skips name resolution stage
/// for such connect messages.
pub fn with(req: T, addr: SocketAddr) -> Connect<T> {
Connect {
req,
port: 0,
addr: Some(Either::Left(addr)),
}
}
/// Use port if address does not provide one.
///
/// By default it set to 0
pub fn set_port(mut self, port: u16) -> Self {
self.port = port;
self
}
/// Use address.
pub fn set_addr(mut self, addr: Option<SocketAddr>) -> Self {
if let Some(addr) = addr {
self.addr = Some(Either::Left(addr));
}
self
}
/// Use addresses.
pub fn set_addrs<I>(mut self, addrs: I) -> Self
where
I: IntoIterator<Item = SocketAddr>,
{
let mut addrs = VecDeque::from_iter(addrs);
self.addr = if addrs.len() < 2 {
addrs.pop_front().map(Either::Left)
} else {
Some(Either::Right(addrs))
};
self
}
/// Host name
pub fn host(&self) -> &str {
self.req.host()
}
/// Port of the request
pub fn port(&self) -> u16 {
self.req.port().unwrap_or(self.port)
}
/// Pre-resolved addresses of the request.
pub fn addrs(&self) -> ConnectAddrsIter<'_> {
let inner = match self.addr {
None => Either::Left(None),
Some(Either::Left(addr)) => Either::Left(Some(addr)),
Some(Either::Right(ref addrs)) => Either::Right(addrs.iter()),
};
ConnectAddrsIter { inner }
}
/// Takes pre-resolved addresses of the request.
pub fn take_addrs(&mut self) -> ConnectTakeAddrsIter {
let inner = match self.addr.take() {
None => Either::Left(None),
Some(Either::Left(addr)) => Either::Left(Some(addr)),
Some(Either::Right(addrs)) => Either::Right(addrs.into_iter()),
};
ConnectTakeAddrsIter { inner }
}
}
impl<T: Address> From<T> for Connect<T> {
fn from(addr: T) -> Self {
Connect::new(addr)
}
}
impl<T: Address> fmt::Display for Connect<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.host(), self.port())
}
}
/// Iterator over addresses in a [`Connect`] request.
#[derive(Clone)]
pub struct ConnectAddrsIter<'a> {
inner: Either<Option<SocketAddr>, vec_deque::Iter<'a, SocketAddr>>,
}
impl Iterator for ConnectAddrsIter<'_> {
type Item = SocketAddr;
fn next(&mut self) -> Option<Self::Item> {
match self.inner {
Either::Left(ref mut opt) => opt.take(),
Either::Right(ref mut iter) => iter.next().copied(),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.inner {
Either::Left(Some(_)) => (1, Some(1)),
Either::Left(None) => (0, Some(0)),
Either::Right(ref iter) => iter.size_hint(),
}
}
}
impl fmt::Debug for ConnectAddrsIter<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.clone()).finish()
}
}
impl ExactSizeIterator for ConnectAddrsIter<'_> {}
impl FusedIterator for ConnectAddrsIter<'_> {}
/// Owned iterator over addresses in a [`Connect`] request.
#[derive(Debug)]
pub struct ConnectTakeAddrsIter {
inner: Either<Option<SocketAddr>, vec_deque::IntoIter<SocketAddr>>,
}
impl Iterator for ConnectTakeAddrsIter {
type Item = SocketAddr;
fn next(&mut self) -> Option<Self::Item> {
match self.inner {
Either::Left(ref mut opt) => opt.take(),
Either::Right(ref mut iter) => iter.next(),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.inner {
Either::Left(Some(_)) => (1, Some(1)),
Either::Left(None) => (0, Some(0)),
Either::Right(ref iter) => iter.size_hint(),
}
}
}
impl ExactSizeIterator for ConnectTakeAddrsIter {}
impl FusedIterator for ConnectTakeAddrsIter {}
fn parse(host: &str) -> (&str, Option<u16>) {
let mut parts_iter = host.splitn(2, ':');
if let Some(host) = parts_iter.next() {
let port_str = parts_iter.next().unwrap_or("");
if let Ok(port) = port_str.parse::<u16>() {
(host, Some(port))
} else {
(host, None)
}
} else {
(host, None)
}
}
pub struct Connection<T, U> {
io: U,
req: T,
}
impl<T, U> Connection<T, U> {
pub fn new(io: U, req: T) -> Self {
Self { io, req }
}
}
impl<T, U> Connection<T, U> {
/// Reconstruct from a parts.
pub fn from_parts(io: U, req: T) -> Self {
Self { io, req }
}
/// Deconstruct into a parts.
pub fn into_parts(self) -> (U, T) {
(self.io, self.req)
}
/// Replace inclosed object, return new Stream and old object
pub fn replace<Y>(self, io: Y) -> (U, Connection<T, Y>) {
(self.io, Connection { io, req: self.req })
}
/// Returns a shared reference to the underlying stream.
pub fn get_ref(&self) -> &U {
&self.io
}
/// Returns a mutable reference to the underlying stream.
pub fn get_mut(&mut self) -> &mut U {
&mut self.io
}
}
impl<T: Address, U> Connection<T, U> {
/// Get request
pub fn host(&self) -> &str {
&self.req.host()
}
}
impl<T, U> std::ops::Deref for Connection<T, U> {
type Target = U;
fn deref(&self) -> &U {
&self.io
}
}
impl<T, U> std::ops::DerefMut for Connection<T, U> {
fn deref_mut(&mut self) -> &mut U {
&mut self.io
}
}
impl<T, U: fmt::Debug> fmt::Debug for Connection<T, U> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Stream {{{:?}}}", self.io)
}
}

View File

@@ -0,0 +1,172 @@
use std::collections::VecDeque;
use std::future::Future;
use std::io;
use std::marker::PhantomData;
use std::net::SocketAddr;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_rt::net::TcpStream;
use actix_service::{Service, ServiceFactory};
use futures_util::future::{err, ok, BoxFuture, Either, FutureExt, Ready};
use super::connect::{Address, Connect, Connection};
use super::error::ConnectError;
/// TCP connector service factory
#[derive(Debug)]
pub struct TcpConnectorFactory<T>(PhantomData<T>);
impl<T> TcpConnectorFactory<T> {
pub fn new() -> Self {
TcpConnectorFactory(PhantomData)
}
/// Create TCP connector service
pub fn service(&self) -> TcpConnector<T> {
TcpConnector(PhantomData)
}
}
impl<T> Default for TcpConnectorFactory<T> {
fn default() -> Self {
TcpConnectorFactory(PhantomData)
}
}
impl<T> Clone for TcpConnectorFactory<T> {
fn clone(&self) -> Self {
TcpConnectorFactory(PhantomData)
}
}
impl<T: Address> ServiceFactory for TcpConnectorFactory<T> {
type Request = Connect<T>;
type Response = Connection<T, TcpStream>;
type Error = ConnectError;
type Config = ();
type Service = TcpConnector<T>;
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future {
ok(self.service())
}
}
/// TCP connector service
#[derive(Default, Debug)]
pub struct TcpConnector<T>(PhantomData<T>);
impl<T> TcpConnector<T> {
pub fn new() -> Self {
TcpConnector(PhantomData)
}
}
impl<T> Clone for TcpConnector<T> {
fn clone(&self) -> Self {
TcpConnector(PhantomData)
}
}
impl<T: Address> Service for TcpConnector<T> {
type Request = Connect<T>;
type Response = Connection<T, TcpStream>;
type Error = ConnectError;
#[allow(clippy::type_complexity)]
type Future = Either<TcpConnectorResponse<T>, Ready<Result<Self::Response, Self::Error>>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Connect<T>) -> Self::Future {
let port = req.port();
let Connect { req, addr, .. } = req;
if let Some(addr) = addr {
Either::Left(TcpConnectorResponse::new(req, port, addr))
} else {
error!("TCP connector: got unresolved address");
Either::Right(err(ConnectError::Unresolved))
}
}
}
#[doc(hidden)]
/// TCP stream connector response future
pub struct TcpConnectorResponse<T> {
req: Option<T>,
port: u16,
addrs: Option<VecDeque<SocketAddr>>,
stream: Option<BoxFuture<'static, Result<TcpStream, io::Error>>>,
}
impl<T: Address> TcpConnectorResponse<T> {
pub fn new(
req: T,
port: u16,
addr: either::Either<SocketAddr, VecDeque<SocketAddr>>,
) -> TcpConnectorResponse<T> {
trace!(
"TCP connector - connecting to {:?} port:{}",
req.host(),
port
);
match addr {
either::Either::Left(addr) => TcpConnectorResponse {
req: Some(req),
port,
addrs: None,
stream: Some(TcpStream::connect(addr).boxed()),
},
either::Either::Right(addrs) => TcpConnectorResponse {
req: Some(req),
port,
addrs: Some(addrs),
stream: None,
},
}
}
}
impl<T: Address> Future for TcpConnectorResponse<T> {
type Output = Result<Connection<T, TcpStream>, ConnectError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
// connect
loop {
if let Some(new) = this.stream.as_mut() {
match new.as_mut().poll(cx) {
Poll::Ready(Ok(sock)) => {
let req = this.req.take().unwrap();
trace!(
"TCP connector - successfully connected to connecting to {:?} - {:?}",
req.host(), sock.peer_addr()
);
return Poll::Ready(Ok(Connection::new(sock, req)));
}
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(err)) => {
trace!(
"TCP connector - failed to connect to connecting to {:?} port: {}",
this.req.as_ref().unwrap().host(),
this.port,
);
if this.addrs.is_none() || this.addrs.as_ref().unwrap().is_empty() {
return Poll::Ready(Err(err.into()));
}
}
}
}
// try to connect
let addr = this.addrs.as_mut().unwrap().pop_front().unwrap();
this.stream = Some(TcpStream::connect(addr).boxed());
}
}
}

View File

@@ -1,12 +1,13 @@
use std::io; use std::io;
use derive_more::Display; use derive_more::{Display, From};
use trust_dns_resolver::error::ResolveError;
#[derive(Debug, Display)] #[derive(Debug, From, Display)]
pub enum ConnectError { pub enum ConnectError {
/// Failed to resolve the hostname /// Failed to resolve the hostname
#[display(fmt = "Failed resolving hostname: {}", _0)] #[display(fmt = "Failed resolving hostname: {}", _0)]
Resolver(Box<dyn std::error::Error>), Resolver(ResolveError),
/// No dns records /// No dns records
#[display(fmt = "No dns records found for the input")] #[display(fmt = "No dns records found for the input")]

113
actix-connect/src/lib.rs Normal file
View File

@@ -0,0 +1,113 @@
//! TCP connector service for Actix ecosystem.
//!
//! ## Package feature
//!
//! * `openssl` - enables TLS support via `openssl` crate
//! * `rustls` - enables TLS support via `rustls` crate
#![deny(rust_2018_idioms, nonstandard_style)]
#![recursion_limit = "128"]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#[macro_use]
extern crate log;
mod connect;
mod connector;
mod error;
mod resolve;
mod service;
pub mod ssl;
#[cfg(feature = "uri")]
mod uri;
use actix_rt::{net::TcpStream, Arbiter};
use actix_service::{pipeline, pipeline_factory, Service, ServiceFactory};
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
use trust_dns_resolver::system_conf::read_system_conf;
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
pub mod resolver {
pub use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
pub use trust_dns_resolver::system_conf::read_system_conf;
pub use trust_dns_resolver::{error::ResolveError, AsyncResolver};
}
pub use self::connect::{Address, Connect, Connection};
pub use self::connector::{TcpConnector, TcpConnectorFactory};
pub use self::error::ConnectError;
pub use self::resolve::{Resolver, ResolverFactory};
pub use self::service::{ConnectService, ConnectServiceFactory, TcpConnectService};
pub async fn start_resolver(
cfg: ResolverConfig,
opts: ResolverOpts,
) -> Result<AsyncResolver, ConnectError> {
Ok(AsyncResolver::tokio(cfg, opts).await?)
}
struct DefaultResolver(AsyncResolver);
pub(crate) async fn get_default_resolver() -> Result<AsyncResolver, ConnectError> {
if Arbiter::contains_item::<DefaultResolver>() {
Ok(Arbiter::get_item(|item: &DefaultResolver| item.0.clone()))
} else {
let (cfg, opts) = match read_system_conf() {
Ok((cfg, opts)) => (cfg, opts),
Err(e) => {
log::error!("TRust-DNS can not load system config: {}", e);
(ResolverConfig::default(), ResolverOpts::default())
}
};
let resolver = AsyncResolver::tokio(cfg, opts).await?;
Arbiter::set_item(DefaultResolver(resolver.clone()));
Ok(resolver)
}
}
pub async fn start_default_resolver() -> Result<AsyncResolver, ConnectError> {
get_default_resolver().await
}
/// Create TCP connector service.
pub fn new_connector<T: Address + 'static>(
resolver: AsyncResolver,
) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError>
+ Clone {
pipeline(Resolver::new(resolver)).and_then(TcpConnector::new())
}
/// Create TCP connector service factory.
pub fn new_connector_factory<T: Address + 'static>(
resolver: AsyncResolver,
) -> impl ServiceFactory<
Config = (),
Request = Connect<T>,
Response = Connection<T, TcpStream>,
Error = ConnectError,
InitError = (),
> + Clone {
pipeline_factory(ResolverFactory::new(resolver)).and_then(TcpConnectorFactory::new())
}
/// Create connector service with default parameters.
pub fn default_connector<T: Address + 'static>(
) -> impl Service<Request = Connect<T>, Response = Connection<T, TcpStream>, Error = ConnectError>
+ Clone {
pipeline(Resolver::default()).and_then(TcpConnector::new())
}
/// Create connector service factory with default parameters.
pub fn default_connector_factory<T: Address + 'static>() -> impl ServiceFactory<
Config = (),
Request = Connect<T>,
Response = Connection<T, TcpStream>,
Error = ConnectError,
InitError = (),
> + Clone {
pipeline_factory(ResolverFactory::default()).and_then(TcpConnectorFactory::new())
}

View File

@@ -0,0 +1,208 @@
use std::future::Future;
use std::marker::PhantomData;
use std::net::SocketAddr;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_service::{Service, ServiceFactory};
use futures_util::future::{ok, Either, Ready};
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
use trust_dns_resolver::{error::ResolveError, lookup_ip::LookupIp};
use crate::connect::{Address, Connect};
use crate::error::ConnectError;
use crate::get_default_resolver;
/// DNS Resolver Service factory
pub struct ResolverFactory<T> {
resolver: Option<AsyncResolver>,
_t: PhantomData<T>,
}
impl<T> ResolverFactory<T> {
/// Create new resolver instance with custom configuration and options.
pub fn new(resolver: AsyncResolver) -> Self {
ResolverFactory {
resolver: Some(resolver),
_t: PhantomData,
}
}
pub fn service(&self) -> Resolver<T> {
Resolver {
resolver: self.resolver.clone(),
_t: PhantomData,
}
}
}
impl<T> Default for ResolverFactory<T> {
fn default() -> Self {
ResolverFactory {
resolver: None,
_t: PhantomData,
}
}
}
impl<T> Clone for ResolverFactory<T> {
fn clone(&self) -> Self {
ResolverFactory {
resolver: self.resolver.clone(),
_t: PhantomData,
}
}
}
impl<T: Address> ServiceFactory for ResolverFactory<T> {
type Request = Connect<T>;
type Response = Connect<T>;
type Error = ConnectError;
type Config = ();
type Service = Resolver<T>;
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future {
ok(self.service())
}
}
/// DNS Resolver Service
pub struct Resolver<T> {
resolver: Option<AsyncResolver>,
_t: PhantomData<T>,
}
impl<T> Resolver<T> {
/// Create new resolver instance with custom configuration and options.
pub fn new(resolver: AsyncResolver) -> Self {
Resolver {
resolver: Some(resolver),
_t: PhantomData,
}
}
}
impl<T> Default for Resolver<T> {
fn default() -> Self {
Resolver {
resolver: None,
_t: PhantomData,
}
}
}
impl<T> Clone for Resolver<T> {
fn clone(&self) -> Self {
Resolver {
resolver: self.resolver.clone(),
_t: PhantomData,
}
}
}
impl<T: Address> Service for Resolver<T> {
type Request = Connect<T>;
type Response = Connect<T>;
type Error = ConnectError;
#[allow(clippy::type_complexity)]
type Future = Either<
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>,
Ready<Result<Connect<T>, Self::Error>>,
>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, mut req: Connect<T>) -> Self::Future {
if req.addr.is_some() {
Either::Right(ok(req))
} else if let Ok(ip) = req.host().parse() {
req.addr = Some(either::Either::Left(SocketAddr::new(ip, req.port())));
Either::Right(ok(req))
} else {
let resolver = self.resolver.as_ref().map(AsyncResolver::clone);
Either::Left(Box::pin(async move {
trace!("DNS resolver: resolving host {:?}", req.host());
let resolver = if let Some(resolver) = resolver {
resolver
} else {
get_default_resolver()
.await
.expect("Failed to get default resolver")
};
ResolverFuture::new(req, &resolver).await
}))
}
}
}
type LookupIpFuture = Pin<Box<dyn Future<Output = Result<LookupIp, ResolveError>>>>;
#[doc(hidden)]
/// Resolver future
pub struct ResolverFuture<T: Address> {
req: Option<Connect<T>>,
lookup: LookupIpFuture,
}
impl<T: Address> ResolverFuture<T> {
pub fn new(req: Connect<T>, resolver: &AsyncResolver) -> Self {
let host = if let Some(host) = req.host().splitn(2, ':').next() {
host
} else {
req.host()
};
// Clone data to be moved to the lookup future
let host_clone = host.to_owned();
let resolver_clone = resolver.clone();
ResolverFuture {
lookup: Box::pin(async move {
let resolver = resolver_clone;
resolver.lookup_ip(host_clone).await
}),
req: Some(req),
}
}
}
impl<T: Address> Future for ResolverFuture<T> {
type Output = Result<Connect<T>, ConnectError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
match Pin::new(&mut this.lookup).poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Ok(ips)) => {
let req = this.req.take().unwrap();
let port = req.port();
let req = req.set_addrs(ips.iter().map(|ip| SocketAddr::new(ip, port)));
trace!(
"DNS resolver: host {:?} resolved to {:?}",
req.host(),
req.addrs()
);
if req.addr.is_none() {
Poll::Ready(Err(ConnectError::NoRecords))
} else {
Poll::Ready(Ok(req))
}
}
Poll::Ready(Err(e)) => {
trace!(
"DNS resolver: failed to resolve host {:?} err: {}",
this.req.as_ref().unwrap().host(),
e
);
Poll::Ready(Err(e.into()))
}
}
}
}

View File

@@ -0,0 +1,232 @@
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_rt::net::TcpStream;
use actix_service::{Service, ServiceFactory};
use either::Either;
use futures_util::future::{ok, Ready};
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
use crate::connect::{Address, Connect, Connection};
use crate::connector::{TcpConnector, TcpConnectorFactory};
use crate::error::ConnectError;
use crate::resolve::{Resolver, ResolverFactory};
pub struct ConnectServiceFactory<T> {
tcp: TcpConnectorFactory<T>,
resolver: ResolverFactory<T>,
}
impl<T> ConnectServiceFactory<T> {
/// Construct new ConnectService factory
pub fn new() -> Self {
ConnectServiceFactory {
tcp: TcpConnectorFactory::default(),
resolver: ResolverFactory::default(),
}
}
/// Construct new connect service with custom dns resolver
pub fn with_resolver(resolver: AsyncResolver) -> Self {
ConnectServiceFactory {
tcp: TcpConnectorFactory::default(),
resolver: ResolverFactory::new(resolver),
}
}
/// Construct new service
pub fn service(&self) -> ConnectService<T> {
ConnectService {
tcp: self.tcp.service(),
resolver: self.resolver.service(),
}
}
/// Construct new tcp stream service
pub fn tcp_service(&self) -> TcpConnectService<T> {
TcpConnectService {
tcp: self.tcp.service(),
resolver: self.resolver.service(),
}
}
}
impl<T> Default for ConnectServiceFactory<T> {
fn default() -> Self {
ConnectServiceFactory {
tcp: TcpConnectorFactory::default(),
resolver: ResolverFactory::default(),
}
}
}
impl<T> Clone for ConnectServiceFactory<T> {
fn clone(&self) -> Self {
ConnectServiceFactory {
tcp: self.tcp.clone(),
resolver: self.resolver.clone(),
}
}
}
impl<T: Address> ServiceFactory for ConnectServiceFactory<T> {
type Request = Connect<T>;
type Response = Connection<T, TcpStream>;
type Error = ConnectError;
type Config = ();
type Service = ConnectService<T>;
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future {
ok(self.service())
}
}
#[derive(Clone)]
pub struct ConnectService<T> {
tcp: TcpConnector<T>,
resolver: Resolver<T>,
}
impl<T: Address> Service for ConnectService<T> {
type Request = Connect<T>;
type Response = Connection<T, TcpStream>;
type Error = ConnectError;
type Future = ConnectServiceResponse<T>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Connect<T>) -> Self::Future {
ConnectServiceResponse {
state: ConnectState::Resolve(self.resolver.call(req)),
tcp: self.tcp.clone(),
}
}
}
enum ConnectState<T: Address> {
Resolve(<Resolver<T> as Service>::Future),
Connect(<TcpConnector<T> as Service>::Future),
}
impl<T: Address> ConnectState<T> {
#[allow(clippy::type_complexity)]
fn poll(
&mut self,
cx: &mut Context<'_>,
) -> Either<Poll<Result<Connection<T, TcpStream>, ConnectError>>, Connect<T>> {
match self {
ConnectState::Resolve(ref mut fut) => match Pin::new(fut).poll(cx) {
Poll::Pending => Either::Left(Poll::Pending),
Poll::Ready(Ok(res)) => Either::Right(res),
Poll::Ready(Err(err)) => Either::Left(Poll::Ready(Err(err))),
},
ConnectState::Connect(ref mut fut) => Either::Left(Pin::new(fut).poll(cx)),
}
}
}
pub struct ConnectServiceResponse<T: Address> {
state: ConnectState<T>,
tcp: TcpConnector<T>,
}
impl<T: Address> Future for ConnectServiceResponse<T> {
type Output = Result<Connection<T, TcpStream>, ConnectError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let res = match self.state.poll(cx) {
Either::Right(res) => {
self.state = ConnectState::Connect(self.tcp.call(res));
self.state.poll(cx)
}
Either::Left(res) => return res,
};
match res {
Either::Left(res) => res,
Either::Right(_) => panic!(),
}
}
}
#[derive(Clone)]
pub struct TcpConnectService<T> {
tcp: TcpConnector<T>,
resolver: Resolver<T>,
}
impl<T: Address + 'static> Service for TcpConnectService<T> {
type Request = Connect<T>;
type Response = TcpStream;
type Error = ConnectError;
type Future = TcpConnectServiceResponse<T>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Connect<T>) -> Self::Future {
TcpConnectServiceResponse {
state: TcpConnectState::Resolve(self.resolver.call(req)),
tcp: self.tcp.clone(),
}
}
}
enum TcpConnectState<T: Address> {
Resolve(<Resolver<T> as Service>::Future),
Connect(<TcpConnector<T> as Service>::Future),
}
impl<T: Address> TcpConnectState<T> {
fn poll(
&mut self,
cx: &mut Context<'_>,
) -> Either<Poll<Result<TcpStream, ConnectError>>, Connect<T>> {
match self {
TcpConnectState::Resolve(ref mut fut) => match Pin::new(fut).poll(cx) {
Poll::Pending => (),
Poll::Ready(Ok(res)) => return Either::Right(res),
Poll::Ready(Err(err)) => return Either::Left(Poll::Ready(Err(err))),
},
TcpConnectState::Connect(ref mut fut) => {
if let Poll::Ready(res) = Pin::new(fut).poll(cx) {
return match res {
Ok(conn) => Either::Left(Poll::Ready(Ok(conn.into_parts().0))),
Err(err) => Either::Left(Poll::Ready(Err(err))),
};
}
}
}
Either::Left(Poll::Pending)
}
}
pub struct TcpConnectServiceResponse<T: Address> {
state: TcpConnectState<T>,
tcp: TcpConnector<T>,
}
impl<T: Address> Future for TcpConnectServiceResponse<T> {
type Output = Result<TcpStream, ConnectError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let res = match self.state.poll(cx) {
Either::Right(res) => {
self.state = TcpConnectState::Connect(self.tcp.call(res));
self.state.poll(cx)
}
Either::Left(res) => return res,
};
match res {
Either::Left(res) => res,
Either::Right(_) => panic!(),
}
}
}

View File

@@ -5,6 +5,3 @@ pub mod openssl;
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
pub mod rustls; pub mod rustls;
#[cfg(feature = "native-tls")]
pub mod native_tls;

View File

@@ -0,0 +1,268 @@
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::{fmt, io};
pub use open_ssl::ssl::{Error as SslError, SslConnector, SslMethod};
pub use tokio_openssl::{HandshakeError, SslStream};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream;
use actix_service::{Service, ServiceFactory};
use futures_util::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready};
use trust_dns_resolver::TokioAsyncResolver as AsyncResolver;
use crate::{
Address, Connect, ConnectError, ConnectService, ConnectServiceFactory, Connection,
};
/// OpenSSL connector factory
pub struct OpensslConnector<T, U> {
connector: SslConnector,
_t: PhantomData<(T, U)>,
}
impl<T, U> OpensslConnector<T, U> {
pub fn new(connector: SslConnector) -> Self {
OpensslConnector {
connector,
_t: PhantomData,
}
}
}
impl<T, U> OpensslConnector<T, U>
where
T: Address + 'static,
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
{
pub fn service(connector: SslConnector) -> OpensslConnectorService<T, U> {
OpensslConnectorService {
connector,
_t: PhantomData,
}
}
}
impl<T, U> Clone for OpensslConnector<T, U> {
fn clone(&self) -> Self {
Self {
connector: self.connector.clone(),
_t: PhantomData,
}
}
}
impl<T, U> ServiceFactory for OpensslConnector<T, U>
where
T: Address + 'static,
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
{
type Request = Connection<T, U>;
type Response = Connection<T, SslStream<U>>;
type Error = io::Error;
type Config = ();
type Service = OpensslConnectorService<T, U>;
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future {
ok(OpensslConnectorService {
connector: self.connector.clone(),
_t: PhantomData,
})
}
}
pub struct OpensslConnectorService<T, U> {
connector: SslConnector,
_t: PhantomData<(T, U)>,
}
impl<T, U> Clone for OpensslConnectorService<T, U> {
fn clone(&self) -> Self {
Self {
connector: self.connector.clone(),
_t: PhantomData,
}
}
}
impl<T, U> Service for OpensslConnectorService<T, U>
where
T: Address + 'static,
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
{
type Request = Connection<T, U>;
type Response = Connection<T, SslStream<U>>;
type Error = io::Error;
#[allow(clippy::type_complexity)]
type Future = Either<ConnectAsyncExt<T, U>, Ready<Result<Self::Response, Self::Error>>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, stream: Connection<T, U>) -> Self::Future {
trace!("SSL Handshake start for: {:?}", stream.host());
let (io, stream) = stream.replace(());
let host = stream.host().to_string();
match self.connector.configure() {
Err(e) => Either::Right(err(io::Error::new(io::ErrorKind::Other, e))),
Ok(config) => Either::Left(ConnectAsyncExt {
fut: async move { tokio_openssl::connect(config, &host, io).await }
.boxed_local(),
stream: Some(stream),
_t: PhantomData,
}),
}
}
}
pub struct ConnectAsyncExt<T, U> {
fut: LocalBoxFuture<'static, Result<SslStream<U>, HandshakeError<U>>>,
stream: Option<Connection<T, ()>>,
_t: PhantomData<U>,
}
impl<T: Address, U> Future for ConnectAsyncExt<T, U>
where
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static,
{
type Output = Result<Connection<T, SslStream<U>>, io::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
match Pin::new(&mut this.fut).poll(cx) {
Poll::Ready(Ok(stream)) => {
let s = this.stream.take().unwrap();
trace!("SSL Handshake success: {:?}", s.host());
Poll::Ready(Ok(s.replace(stream).1))
}
Poll::Ready(Err(e)) => {
trace!("SSL Handshake error: {:?}", e);
Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, format!("{}", e))))
}
Poll::Pending => Poll::Pending,
}
}
}
pub struct OpensslConnectServiceFactory<T> {
tcp: ConnectServiceFactory<T>,
openssl: OpensslConnector<T, TcpStream>,
}
impl<T> OpensslConnectServiceFactory<T> {
/// Construct new OpensslConnectService factory
pub fn new(connector: SslConnector) -> Self {
OpensslConnectServiceFactory {
tcp: ConnectServiceFactory::default(),
openssl: OpensslConnector::new(connector),
}
}
/// Construct new connect service with custom DNS resolver
pub fn with_resolver(connector: SslConnector, resolver: AsyncResolver) -> Self {
OpensslConnectServiceFactory {
tcp: ConnectServiceFactory::with_resolver(resolver),
openssl: OpensslConnector::new(connector),
}
}
/// Construct OpenSSL connect service
pub fn service(&self) -> OpensslConnectService<T> {
OpensslConnectService {
tcp: self.tcp.service(),
openssl: OpensslConnectorService {
connector: self.openssl.connector.clone(),
_t: PhantomData,
},
}
}
}
impl<T> Clone for OpensslConnectServiceFactory<T> {
fn clone(&self) -> Self {
OpensslConnectServiceFactory {
tcp: self.tcp.clone(),
openssl: self.openssl.clone(),
}
}
}
impl<T: Address + 'static> ServiceFactory for OpensslConnectServiceFactory<T> {
type Request = Connect<T>;
type Response = SslStream<TcpStream>;
type Error = ConnectError;
type Config = ();
type Service = OpensslConnectService<T>;
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future {
ok(self.service())
}
}
#[derive(Clone)]
pub struct OpensslConnectService<T> {
tcp: ConnectService<T>,
openssl: OpensslConnectorService<T, TcpStream>,
}
impl<T: Address + 'static> Service for OpensslConnectService<T> {
type Request = Connect<T>;
type Response = SslStream<TcpStream>;
type Error = ConnectError;
type Future = OpensslConnectServiceResponse<T>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Connect<T>) -> Self::Future {
OpensslConnectServiceResponse {
fut1: Some(self.tcp.call(req)),
fut2: None,
openssl: self.openssl.clone(),
}
}
}
pub struct OpensslConnectServiceResponse<T: Address + 'static> {
fut1: Option<<ConnectService<T> as Service>::Future>,
fut2: Option<<OpensslConnectorService<T, TcpStream> as Service>::Future>,
openssl: OpensslConnectorService<T, TcpStream>,
}
impl<T: Address> Future for OpensslConnectServiceResponse<T> {
type Output = Result<SslStream<TcpStream>, ConnectError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(ref mut fut) = self.fut1 {
match futures_util::ready!(Pin::new(fut).poll(cx)) {
Ok(res) => {
let _ = self.fut1.take();
self.fut2 = Some(self.openssl.call(res));
}
Err(e) => return Poll::Ready(Err(e)),
}
}
if let Some(ref mut fut) = self.fut2 {
match futures_util::ready!(Pin::new(fut).poll(cx)) {
Ok(connect) => Poll::Ready(Ok(connect.into_parts().0)),
Err(e) => Poll::Ready(Err(ConnectError::Io(io::Error::new(
io::ErrorKind::Other,
e,
)))),
}
} else {
Poll::Pending
}
}
}

View File

@@ -0,0 +1,136 @@
use std::fmt;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
pub use rust_tls::Session;
pub use tokio_rustls::{client::TlsStream, rustls::ClientConfig};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_service::{Service, ServiceFactory};
use futures_util::future::{ok, Ready};
use tokio_rustls::{Connect, TlsConnector};
use webpki::DNSNameRef;
use crate::{Address, Connection};
/// Rustls connector factory
pub struct RustlsConnector<T, U> {
connector: Arc<ClientConfig>,
_t: PhantomData<(T, U)>,
}
impl<T, U> RustlsConnector<T, U> {
pub fn new(connector: Arc<ClientConfig>) -> Self {
RustlsConnector {
connector,
_t: PhantomData,
}
}
}
impl<T, U> RustlsConnector<T, U>
where
T: Address,
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
{
pub fn service(connector: Arc<ClientConfig>) -> RustlsConnectorService<T, U> {
RustlsConnectorService {
connector,
_t: PhantomData,
}
}
}
impl<T, U> Clone for RustlsConnector<T, U> {
fn clone(&self) -> Self {
Self {
connector: self.connector.clone(),
_t: PhantomData,
}
}
}
impl<T: Address, U> ServiceFactory for RustlsConnector<T, U>
where
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
{
type Request = Connection<T, U>;
type Response = Connection<T, TlsStream<U>>;
type Error = std::io::Error;
type Config = ();
type Service = RustlsConnectorService<T, U>;
type InitError = ();
type Future = Ready<Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future {
ok(RustlsConnectorService {
connector: self.connector.clone(),
_t: PhantomData,
})
}
}
pub struct RustlsConnectorService<T, U> {
connector: Arc<ClientConfig>,
_t: PhantomData<(T, U)>,
}
impl<T, U> Clone for RustlsConnectorService<T, U> {
fn clone(&self) -> Self {
Self {
connector: self.connector.clone(),
_t: PhantomData,
}
}
}
impl<T: Address, U> Service for RustlsConnectorService<T, U>
where
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
{
type Request = Connection<T, U>;
type Response = Connection<T, TlsStream<U>>;
type Error = std::io::Error;
type Future = ConnectAsyncExt<T, U>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, stream: Connection<T, U>) -> Self::Future {
trace!("SSL Handshake start for: {:?}", stream.host());
let (io, stream) = stream.replace(());
let host = DNSNameRef::try_from_ascii_str(stream.host())
.expect("rustls currently only handles hostname-based connections. See https://github.com/briansmith/webpki/issues/54");
ConnectAsyncExt {
fut: TlsConnector::from(self.connector.clone()).connect(host, io),
stream: Some(stream),
}
}
}
pub struct ConnectAsyncExt<T, U> {
fut: Connect<U>,
stream: Option<Connection<T, ()>>,
}
impl<T: Address, U> Future for ConnectAsyncExt<T, U>
where
U: AsyncRead + AsyncWrite + Unpin + fmt::Debug,
{
type Output = Result<Connection<T, TlsStream<U>>, std::io::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
Poll::Ready(
futures_util::ready!(Pin::new(&mut this.fut).poll(cx)).map(|stream| {
let s = this.stream.take().unwrap();
trace!("SSL Handshake success: {:?}", s.host());
s.replace(stream).1
}),
)
}
}

37
actix-connect/src/uri.rs Normal file
View File

@@ -0,0 +1,37 @@
use http::Uri;
use crate::Address;
impl Address for Uri {
fn host(&self) -> &str {
self.host().unwrap_or("")
}
fn port(&self) -> Option<u16> {
if let Some(port) = self.port_u16() {
Some(port)
} else {
port(self.scheme_str())
}
}
}
// TODO: load data from file
fn port(scheme: Option<&str>) -> Option<u16> {
if let Some(scheme) = scheme {
match scheme {
"http" => Some(80),
"https" => Some(443),
"ws" => Some(80),
"wss" => Some(443),
"amqp" => Some(5672),
"amqps" => Some(5671),
"sb" => Some(5671),
"mqtt" => Some(1883),
"mqtts" => Some(8883),
_ => None,
}
} else {
None
}
}

View File

@@ -1,18 +1,14 @@
#![cfg(feature = "connect")] use std::io;
use std::{
io,
net::{IpAddr, Ipv4Addr},
};
use actix_codec::{BytesCodec, Framed}; use actix_codec::{BytesCodec, Framed};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
use actix_server::TestServer;
use actix_service::{fn_service, Service, ServiceFactory}; use actix_service::{fn_service, Service, ServiceFactory};
use actix_testing::TestServer;
use bytes::Bytes; use bytes::Bytes;
use futures_util::sink::SinkExt; use futures_util::sink::SinkExt;
use actix_tls::connect::{self as actix_connect, Connect}; use actix_connect::resolver::{ResolverConfig, ResolverOpts};
use actix_connect::Connect;
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
#[actix_rt::test] #[actix_rt::test]
@@ -25,7 +21,7 @@ async fn test_string() {
}) })
}); });
let conn = actix_connect::default_connector(); let mut conn = actix_connect::default_connector();
let addr = format!("localhost:{}", srv.port()); let addr = format!("localhost:{}", srv.port());
let con = conn.call(addr.into()).await.unwrap(); let con = conn.call(addr.into()).await.unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
@@ -42,7 +38,7 @@ async fn test_rustls_string() {
}) })
}); });
let conn = actix_connect::default_connector(); let mut conn = actix_connect::default_connector();
let addr = format!("localhost:{}", srv.port()); let addr = format!("localhost:{}", srv.port());
let con = conn.call(addr.into()).await.unwrap(); let con = conn.call(addr.into()).await.unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
@@ -58,17 +54,14 @@ async fn test_static_str() {
}) })
}); });
let conn = actix_connect::default_connector(); let resolver = actix_connect::start_default_resolver().await.unwrap();
let mut conn = actix_connect::new_connector(resolver.clone());
let con = conn let con = conn.call(Connect::with("10", srv.addr())).await.unwrap();
.call(Connect::with_addr("10", srv.addr()))
.await
.unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
let connect = Connect::new(srv.host().to_owned()); let connect = Connect::new(srv.host().to_owned());
let mut conn = actix_connect::new_connector(resolver);
let conn = actix_connect::default_connector();
let con = conn.call(connect).await; let con = conn.call(connect).await;
assert!(con.is_err()); assert!(con.is_err());
} }
@@ -83,13 +76,15 @@ async fn test_new_service() {
}) })
}); });
let factory = actix_connect::default_connector_factory(); let resolver =
actix_connect::start_resolver(ResolverConfig::default(), ResolverOpts::default())
.await
.unwrap();
let conn = factory.new_service(()).await.unwrap(); let factory = actix_connect::new_connector_factory(resolver);
let con = conn
.call(Connect::with_addr("10", srv.addr())) let mut conn = factory.new_service(()).await.unwrap();
.await let con = conn.call(Connect::with("10", srv.addr())).await.unwrap();
.unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
} }
@@ -106,7 +101,7 @@ async fn test_openssl_uri() {
}) })
}); });
let conn = actix_connect::default_connector(); let mut conn = actix_connect::default_connector();
let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap(); let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
let con = conn.call(addr.into()).await.unwrap(); let con = conn.call(addr.into()).await.unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
@@ -125,30 +120,8 @@ async fn test_rustls_uri() {
}) })
}); });
let conn = actix_connect::default_connector(); let mut conn = actix_connect::default_connector();
let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap(); let addr = http::Uri::try_from(format!("https://localhost:{}", srv.port())).unwrap();
let con = conn.call(addr.into()).await.unwrap(); let con = conn.call(addr.into()).await.unwrap();
assert_eq!(con.peer_addr().unwrap(), srv.addr()); assert_eq!(con.peer_addr().unwrap(), srv.addr());
} }
#[actix_rt::test]
async fn test_local_addr() {
let srv = TestServer::with(|| {
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 = actix_connect::default_connector();
let local = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 3));
let (con, _) = conn
.call(Connect::with_addr("10", srv.addr()).set_local_addr(local))
.await
.unwrap()
.into_parts();
assert_eq!(con.local_addr().unwrap().ip(), local)
}

View File

@@ -1,27 +1,13 @@
# Changes # CHANGES
## Unreleased - 2021-xx-xx ## 0.1.3 - 2020-12-3
## 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 - 2021-01-09
* Remove `actix-reexport` feature. [#218]
[#218]: https://github.com/actix/actix-net/pull/218
## 0.1.3 - 2020-12-03
* Add `actix-reexport` feature. [#218]
[#218]: https://github.com/actix/actix-net/pull/218
* Add `actix-reexport` feature
## 0.1.2 - 2020-05-18 ## 0.1.2 - 2020-05-18
### Changed
* Forward actix_rt::test arguments to test function [#127] * Forward actix_rt::test arguments to test function [#127]
[#127]: https://github.com/actix/actix-net/pull/127 [#127]: https://github.com/actix/actix-net/pull/127

View File

@@ -1,10 +1,10 @@
[package] [package]
name = "actix-macros" name = "actix-macros"
version = "0.2.0" version = "0.1.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Macros for Actix system and runtime" description = "Actix runtime macros"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net"
documentation = "https://docs.rs/actix-macros" documentation = "https://docs.rs/actix-macros/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
@@ -16,8 +16,11 @@ proc-macro = true
quote = "1.0.3" quote = "1.0.3"
syn = { version = "^1", features = ["full"] } syn = { version = "^1", features = ["full"] }
[features]
actix-reexport = []
[dev-dependencies] [dev-dependencies]
actix-rt = "2.0.0" actix-rt = "1.0"
futures-util = { version = "0.3", default-features = false } futures-util = { version = "0.3", default-features = false }
trybuild = "1" trybuild = "1"

View File

@@ -1,12 +1,4 @@
//! Macros for Actix system and runtime. //! Macros for use with Tokio
//!
//! The [`actix-rt`](https://docs.rs/actix-rt) crate must be available for macro output to compile.
//!
//! # Entry-point
//! See docs for the [`#[main]`](macro@main) macro.
//!
//! # Tests
//! See docs for the [`#[test]`](macro@test) macro.
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_logo_url = "https://actix.rs/img/logo.png")]
@@ -15,10 +7,11 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
/// Marks async entry-point function to be executed by Actix system. /// Marks async function to be executed by actix system.
/// ///
/// # Examples /// ## Usage
/// ``` ///
/// ```rust
/// #[actix_rt::main] /// #[actix_rt::main]
/// async fn main() { /// async fn main() {
/// println!("Hello world"); /// println!("Hello world");
@@ -33,32 +26,42 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
let vis = &input.vis; let vis = &input.vis;
let sig = &mut input.sig; let sig = &mut input.sig;
let body = &input.block; let body = &input.block;
let name = &sig.ident;
if sig.asyncness.is_none() { if sig.asyncness.is_none() {
return syn::Error::new_spanned( return syn::Error::new_spanned(sig.fn_token, "only async fn is supported")
sig.fn_token, .to_compile_error()
"the async keyword is missing from the function declaration", .into();
)
.to_compile_error()
.into();
} }
sig.asyncness = None; sig.asyncness = None;
(quote! { if cfg!(feature = "actix-reexport") {
#(#attrs)* (quote! {
#vis #sig { #(#attrs)*
actix_rt::System::new() #vis #sig {
.block_on(async move { #body }) actix::System::new(stringify!(#name))
} .block_on(async move { #body })
}) }
.into() })
.into()
} else {
(quote! {
#(#attrs)*
#vis #sig {
actix_rt::System::new(stringify!(#name))
.block_on(async move { #body })
}
})
.into()
}
} }
/// Marks async test function to be executed in an Actix system. /// Marks async test function to be executed by actix runtime.
/// ///
/// # Examples /// ## Usage
/// ``` ///
/// ```no_run
/// #[actix_rt::test] /// #[actix_rt::test]
/// async fn my_test() { /// async fn my_test() {
/// assert!(true); /// assert!(true);
@@ -82,7 +85,7 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
if sig.asyncness.is_none() { if sig.asyncness.is_none() {
return syn::Error::new_spanned( return syn::Error::new_spanned(
input.sig.fn_token, input.sig.fn_token,
"the async keyword is missing from the function declaration", format!("only async fn is supported, {}", input.sig.ident),
) )
.to_compile_error() .to_compile_error()
.into(); .into();
@@ -90,19 +93,24 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
sig.asyncness = None; sig.asyncness = None;
let missing_test_attr = if has_test_attr { let result = if has_test_attr {
quote!() quote! {
#(#attrs)*
#vis #sig {
actix_rt::System::new("test")
.block_on(async { #body })
}
}
} else { } else {
quote!(#[test]) quote! {
#[test]
#(#attrs)*
#vis #sig {
actix_rt::System::new("test")
.block_on(async { #body })
}
}
}; };
(quote! { result.into()
#missing_test_attr
#(#attrs)*
#vis #sig {
actix_rt::System::new()
.block_on(async { #body })
}
})
.into()
} }

View File

@@ -3,9 +3,7 @@ fn compile_macros() {
let t = trybuild::TestCases::new(); let t = trybuild::TestCases::new();
t.pass("tests/trybuild/main-01-basic.rs"); t.pass("tests/trybuild/main-01-basic.rs");
t.compile_fail("tests/trybuild/main-02-only-async.rs"); t.compile_fail("tests/trybuild/main-02-only-async.rs");
t.pass("tests/trybuild/main-03-fn-params.rs");
t.pass("tests/trybuild/test-01-basic.rs"); t.pass("tests/trybuild/test-01-basic.rs");
t.pass("tests/trybuild/test-02-keep-attrs.rs"); t.pass("tests/trybuild/test-02-keep-attrs.rs");
t.compile_fail("tests/trybuild/test-03-only-async.rs");
} }

View File

@@ -1,4 +1,4 @@
error: the async keyword is missing from the function declaration error: only async fn is supported
--> $DIR/main-02-only-async.rs:2:1 --> $DIR/main-02-only-async.rs:2:1
| |
2 | fn main() { 2 | fn main() {

View File

@@ -1,6 +0,0 @@
#[actix_rt::main]
async fn main2(_param: bool) {
futures_util::future::ready(()).await
}
fn main() {}

View File

@@ -1,6 +0,0 @@
#[actix_rt::test]
fn my_test() {
futures_util::future::ready(()).await
}
fn main() {}

View File

@@ -1,5 +0,0 @@
error: the async keyword is missing from the function declaration
--> $DIR/test-03-only-async.rs:2:1
|
2 | fn my_test() {
| ^^

View File

@@ -1,98 +1,25 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2020-xx-xx
### Added
## 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.
[#293] https://github.com/actix/actix-net/pull/293
## 2.1.0 - 2021-02-24
* Add `ActixStream` extension trait to include readiness methods. [#276]
* Re-export `tokio::net::TcpSocket` in `net` module [#282]
[#276]: https://github.com/actix/actix-net/pull/276
[#282]: https://github.com/actix/actix-net/pull/282
## 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]
[#274]: https://github.com/actix/actix-net/pull/274
[#275]: https://github.com/actix/actix-net/pull/275
## 2.0.1 - 2021-02-06
* Expose `JoinError` from Tokio. [#271]
[#271]: https://github.com/actix/actix-net/pull/271
## 2.0.0 - 2021-02-02
* Remove all Arbiter-local storage methods. [#262]
* Re-export `tokio::pin`. [#262]
[#262]: https://github.com/actix/actix-net/pull/262
## 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]
[#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
## 2.0.0-beta.2 - 2021-01-09
* Add `task` mod with re-export of `tokio::task::{spawn_blocking, yield_now, JoinHandle}` [#245]
* 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 - 2020-12-28
* Add `System::attach_to_tokio` method. [#173] * 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]
* 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]
[#207]: https://github.com/actix/actix-net/pull/207 ## [1.1.1] - 2020-04-30
[#236]: https://github.com/actix/actix-net/pull/236
### Fixed
## 1.1.1 - 2020-04-30
* Fix memory leak due to [#94] (see [#129] for more detail) * Fix memory leak due to [#94] (see [#129] for more detail)
[#129]: https://github.com/actix/actix-net/issues/129 [#129]: https://github.com/actix/actix-net/issues/129
## [1.1.0] - 2020-04-08
**This version has been yanked.**
### Added
## 1.1.0 - 2020-04-08 (YANKED)
* Expose `System::is_set` to check if current system has ben started [#99] * 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::is_running` to check if event loop is running [#124]
* Add `Arbiter::local_join` associated function * Add `Arbiter::local_join` associated function
@@ -102,57 +29,96 @@
[#99]: https://github.com/actix/actix-net/pull/99 [#99]: https://github.com/actix/actix-net/pull/99
[#124]: https://github.com/actix/actix-net/pull/124 [#124]: https://github.com/actix/actix-net/pull/124
## [1.0.0] - 2019-12-11
## 1.0.0 - 2019-12-11
* Update dependencies * Update dependencies
## [1.0.0-alpha.3] - 2019-12-07
### Fixed
## 1.0.0-alpha.3 - 2019-12-07
* Migrate to tokio 0.2
* Fix compilation on non-unix platforms * Fix compilation on non-unix platforms
### Changed
* Migrate to tokio 0.2
## [1.0.0-alpha.2] - 2019-12-02
Added
## 1.0.0-alpha.2 - 2019-12-02
* Export `main` and `test` attribute macros * Export `main` and `test` attribute macros
* Export `time` module (re-export of tokio-timer) * Export `time` module (re-export of tokio-timer)
* Export `net` module (re-export of tokio-net) * Export `net` module (re-export of tokio-net)
## 1.0.0-alpha.1 - 2019-11-22 ## [1.0.0-alpha.1] - 2019-11-22
### Changed
* Migrate to std::future and tokio 0.2 * Migrate to std::future and tokio 0.2
## 0.2.6 - 2019-11-14 ## [0.2.6] - 2019-11-14
* Allow to join arbiter's thread. #60
### Fixed
* Fix arbiter's thread panic message. * Fix arbiter's thread panic message.
### Added
* Allow to join arbiter's thread. #60
## [0.2.5] - 2019-09-02
### Added
## 0.2.5 - 2019-09-02
* Add arbiter specific storage * Add arbiter specific storage
## 0.2.4 - 2019-07-17 ## [0.2.4] - 2019-07-17
### Changed
* Avoid a copy of the Future when initializing the Box. #29 * Avoid a copy of the Future when initializing the Box. #29
## 0.2.3 - 2019-06-22 ## [0.2.3] - 2019-06-22
* Allow to start System using existing CurrentThread Handle #22
### Added
* Allow to start System using exsiting CurrentThread Handle #22
## 0.2.2 - 2019-03-28 ## [0.2.2] - 2019-03-28
### Changed
* Moved `blocking` module to `actix-threadpool` crate * Moved `blocking` module to `actix-threadpool` crate
## 0.2.1 - 2019-03-11 ## [0.2.1] - 2019-03-11
### Added
* Added `blocking` module * 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 * Arbiter::exec_fn - execute fn on the arbiter's thread
* Arbiter::exec - execute fn on the arbiter's thread and wait result
## 0.2.0 - 2019-03-06 ## [0.2.0] - 2019-03-06
* `run` method returns `io::Result<()>` * `run` method returns `io::Result<()>`
* Removed `Handle` * Removed `Handle`
## 0.1.0 - 2018-12-09 ## [0.1.0] - 2018-12-09
* Initial release * Initial release

View File

@@ -1,15 +1,12 @@
[package] [package]
name = "actix-rt" name = "actix-rt"
version = "2.2.0" version = "1.1.1"
authors = [ authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
"Nikolay Kim <fafhrd91@gmail.com>", description = "Actix runtime"
"Rob Ede <robjtede@icloud.com>", keywords = ["network", "framework", "async", "futures"]
]
description = "Tokio-based single-threaded async runtime for the Actix ecosystem"
keywords = ["async", "futures", "io", "runtime"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net" repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-rt" documentation = "https://docs.rs/actix-rt/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
@@ -18,16 +15,13 @@ edition = "2018"
name = "actix_rt" name = "actix_rt"
path = "src/lib.rs" path = "src/lib.rs"
[features]
default = ["macros"]
macros = ["actix-macros"]
[dependencies] [dependencies]
actix-macros = { version = "0.2.0", optional = true } actix-macros = "0.1.0"
copyless = "0.1.4"
futures-core = { version = "0.3", default-features = false } futures-channel = "0.3.4"
tokio = { version = "1.3", features = ["rt", "net", "parking_lot", "signal", "sync", "time"] } futures-util = { version = "0.3.4", default-features = false, features = ["alloc"] }
smallvec = "1"
tokio = { version = "0.2.6", default-features = false, features = ["rt-core", "rt-util", "io-driver", "tcp", "uds", "udp", "time", "signal", "stream"] }
[dev-dependencies] [dev-dependencies]
tokio = { version = "1.2", features = ["full"] } tokio = { version = "0.2.6", features = ["full"] }
hyper = { version = "0.14", default-features = false, features = ["server", "tcp", "http1"] }

View File

@@ -1,14 +0,0 @@
# actix-rt
> 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.2.0)](https://docs.rs/actix-rt/2.2.0)
[![Version](https://img.shields.io/badge/rustc-1.46+-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)
<br />
[![dependency status](https://deps.rs/crate/actix-rt/2.2.0/status.svg)](https://deps.rs/crate/actix-rt/2.2.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)
See crate documentation for more: https://docs.rs/actix-rt.

View File

@@ -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<Body>) -> Result<Response<Body>, 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(e) = server.await {
eprintln!("server error: {}", e);
}
})
}

View File

@@ -1,60 +0,0 @@
//! An example on how to build a multi-thread tokio runtime for Actix System.
//! Then spawn async task that can make use of work stealing of tokio runtime.
use actix_rt::System;
fn main() {
System::with_tokio_rt(|| {
// build system with a multi-thread tokio runtime.
tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.unwrap()
})
.block_on(async_main());
}
// async main function that acts like #[actix_web::main] or #[tokio::main]
async fn async_main() {
let (tx, rx) = tokio::sync::oneshot::channel();
// get a handle to system arbiter and spawn async task on it
System::current().arbiter().spawn(async {
// use tokio::spawn to get inside the context of multi thread tokio runtime
let h1 = tokio::spawn(async {
println!("thread id is {:?}", std::thread::current().id());
std::thread::sleep(std::time::Duration::from_secs(2));
});
// work stealing occurs for this task spawn
let h2 = tokio::spawn(async {
println!("thread id is {:?}", std::thread::current().id());
});
h1.await.unwrap();
h2.await.unwrap();
let _ = tx.send(());
});
rx.await.unwrap();
let (tx, rx) = tokio::sync::oneshot::channel();
let now = std::time::Instant::now();
// without additional tokio::spawn, all spawned tasks run on single thread
System::current().arbiter().spawn(async {
println!("thread id is {:?}", std::thread::current().id());
std::thread::sleep(std::time::Duration::from_secs(2));
let _ = tx.send(());
});
// previous spawn task has blocked the system arbiter thread
// so this task will wait for 2 seconds until it can be run
System::current().arbiter().spawn(async move {
println!("thread id is {:?}", std::thread::current().id());
assert!(now.elapsed() > std::time::Duration::from_secs(2));
});
rx.await.unwrap();
}

View File

@@ -1,30 +1,40 @@
use std::{ use std::any::{Any, TypeId};
cell::RefCell, use std::cell::{Cell, RefCell};
fmt, use std::collections::HashMap;
future::Future, use std::pin::Pin;
pin::Pin, use std::sync::atomic::{AtomicUsize, Ordering};
sync::atomic::{AtomicUsize, Ordering}, use std::task::{Context, Poll};
task::{Context, Poll}, use std::{fmt, thread};
thread,
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures_channel::oneshot::{channel, Canceled, Sender};
use futures_util::{
future::{self, Future, FutureExt},
stream::Stream,
}; };
use futures_core::ready; use crate::runtime::Runtime;
use tokio::{sync::mpsc, task::LocalSet}; use crate::system::System;
use crate::{ use copyless::BoxHelper;
runtime::{default_tokio_runtime, Runtime},
system::{System, SystemCommand}, use smallvec::SmallVec;
}; pub use tokio::task::JoinHandle;
thread_local!(
static ADDR: RefCell<Option<Arbiter>> = RefCell::new(None);
static RUNNING: Cell<bool> = Cell::new(false);
static Q: RefCell<Vec<Pin<Box<dyn Future<Output = ()>>>>> = RefCell::new(Vec::new());
static PENDING: RefCell<SmallVec<[JoinHandle<()>; 8]>> = RefCell::new(SmallVec::new());
static STORAGE: RefCell<HashMap<TypeId, Box<dyn Any>>> = RefCell::new(HashMap::new());
);
pub(crate) static COUNT: AtomicUsize = AtomicUsize::new(0); pub(crate) static COUNT: AtomicUsize = AtomicUsize::new(0);
thread_local!(
static HANDLE: RefCell<Option<ArbiterHandle>> = RefCell::new(None);
);
pub(crate) enum ArbiterCommand { pub(crate) enum ArbiterCommand {
Stop, Stop,
Execute(Pin<Box<dyn Future<Output = ()> + Send>>), Execute(Box<dyn Future<Output = ()> + Unpin + Send>),
ExecuteFn(Box<dyn FnExec>),
} }
impl fmt::Debug for ArbiterCommand { impl fmt::Debug for ArbiterCommand {
@@ -32,229 +42,427 @@ impl fmt::Debug for ArbiterCommand {
match self { match self {
ArbiterCommand::Stop => write!(f, "ArbiterCommand::Stop"), ArbiterCommand::Stop => write!(f, "ArbiterCommand::Stop"),
ArbiterCommand::Execute(_) => write!(f, "ArbiterCommand::Execute"), ArbiterCommand::Execute(_) => write!(f, "ArbiterCommand::Execute"),
ArbiterCommand::ExecuteFn(_) => write!(f, "ArbiterCommand::ExecuteFn"),
} }
} }
} }
/// A handle for sending spawn and stop messages to an [Arbiter].
#[derive(Debug, Clone)]
pub struct ArbiterHandle {
tx: mpsc::UnboundedSender<ArbiterCommand>,
}
impl ArbiterHandle {
pub(crate) fn new(tx: mpsc::UnboundedSender<ArbiterCommand>) -> Self {
Self { tx }
}
/// Send a future to the [Arbiter]'s thread and spawn it.
///
/// 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.
pub fn spawn<Fut>(&self, future: Fut) -> bool
where
Fut: Future<Output = ()> + Send + 'static,
{
self.tx
.send(ArbiterCommand::Execute(Box::pin(future)))
.is_ok()
}
/// Send a function to the [Arbiter]'s thread and execute it.
///
/// Any result from the function is discarded. If you require a result, include a response
/// channel in the function.
///
/// Returns true if function was sent successfully and false if the [Arbiter] has died.
pub fn spawn_fn<F>(&self, f: F) -> bool
where
F: FnOnce() + Send + 'static,
{
self.spawn(async { f() })
}
/// Instruct [Arbiter] to stop processing it's event loop.
///
/// Returns true if stop message was sent successfully and false if the [Arbiter] has
/// been dropped.
pub fn stop(&self) -> bool {
self.tx.send(ArbiterCommand::Stop).is_ok()
}
}
/// An Arbiter represents a thread that provides an asynchronous execution environment for futures
/// and functions.
///
/// When an arbiter is created, it spawns a new [OS thread](thread), and hosts an event loop.
#[derive(Debug)] #[derive(Debug)]
/// Arbiters provide an asynchronous execution environment for actors, functions
/// and futures. When an Arbiter is created, it spawns a new OS thread, and
/// hosts an event loop. Some Arbiter functions execute on the current thread.
pub struct Arbiter { pub struct Arbiter {
tx: mpsc::UnboundedSender<ArbiterCommand>, sender: UnboundedSender<ArbiterCommand>,
thread_handle: thread::JoinHandle<()>, thread_handle: Option<thread::JoinHandle<()>>,
}
impl Clone for Arbiter {
fn clone(&self) -> Self {
Self::with_sender(self.sender.clone())
}
}
impl Default for Arbiter {
fn default() -> Self {
Self::new()
}
} }
impl Arbiter { impl Arbiter {
/// Spawn a new Arbiter thread and start its event loop. pub(crate) fn new_system() -> Self {
/// let (tx, rx) = unbounded();
/// # Panics
/// Panics if a [System] is not registered on the current thread. let arb = Arbiter::with_sender(tx);
#[allow(clippy::new_without_default)] ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
pub fn new() -> Arbiter { RUNNING.with(|cell| cell.set(false));
Self::with_tokio_rt(|| { STORAGE.with(|cell| cell.borrow_mut().clear());
default_tokio_runtime().expect("Cannot create new Arbiter's Runtime.") Arbiter::spawn(ArbiterController { stop: None, rx });
arb
}
/// Returns the current thread's arbiter's address. If no Arbiter is present, then this
/// function will panic!
pub fn current() -> Arbiter {
ADDR.with(|cell| match *cell.borrow() {
Some(ref addr) => addr.clone(),
None => panic!("Arbiter is not running"),
}) })
} }
/// Spawn a new Arbiter using the [Tokio Runtime](tokio-runtime) returned from a closure. /// Check if current arbiter is running.
/// pub fn is_running() -> bool {
/// [tokio-runtime]: tokio::runtime::Runtime RUNNING.with(|cell| cell.get())
#[doc(hidden)] }
pub fn with_tokio_rt<F>(runtime_factory: F) -> Arbiter
where /// Stop arbiter from continuing it's event loop.
F: Fn() -> tokio::runtime::Runtime + Send + 'static, pub fn stop(&self) {
{ let _ = self.sender.unbounded_send(ArbiterCommand::Stop);
}
/// Spawn new thread and run event loop in spawned thread.
/// Returns address of newly created arbiter.
pub fn new() -> Arbiter {
let id = COUNT.fetch_add(1, Ordering::Relaxed);
let name = format!("actix-rt:worker:{}", id);
let sys = System::current(); let sys = System::current();
let system_id = sys.id(); let (arb_tx, arb_rx) = unbounded();
let arb_id = COUNT.fetch_add(1, Ordering::Relaxed); let arb_tx2 = arb_tx.clone();
let name = format!("actix-rt|system:{}|arbiter:{}", system_id, arb_id); let handle = thread::Builder::new()
let (tx, rx) = mpsc::unbounded_channel();
let (ready_tx, ready_rx) = std::sync::mpsc::channel::<()>();
let thread_handle = thread::Builder::new()
.name(name.clone()) .name(name.clone())
.spawn({ .spawn(move || {
let tx = tx.clone(); let mut rt = Runtime::new().expect("Can not create Runtime");
move || { let arb = Arbiter::with_sender(arb_tx);
let rt = Runtime::from(runtime_factory());
let hnd = ArbiterHandle::new(tx);
System::set_current(sys); let (stop, stop_rx) = channel();
RUNNING.with(|cell| cell.set(true));
STORAGE.with(|cell| cell.borrow_mut().clear());
HANDLE.with(|cell| *cell.borrow_mut() = Some(hnd.clone())); System::set_current(sys);
// register arbiter // start arbiter controller
let _ = System::current() rt.spawn(ArbiterController {
.tx() stop: Some(stop),
.send(SystemCommand::RegisterArbiter(arb_id, hnd)); rx: arb_rx,
});
ADDR.with(|cell| *cell.borrow_mut() = Some(arb.clone()));
ready_tx.send(()).unwrap(); // register arbiter
let _ = System::current()
.sys()
.unbounded_send(SystemCommand::RegisterArbiter(id, arb));
// run arbiter event processing loop // run loop
rt.block_on(ArbiterRunner { rx }); let _ = rt.block_on(stop_rx).unwrap_or(1);
// deregister arbiter // unregister arbiter
let _ = System::current() let _ = System::current()
.tx() .sys()
.send(SystemCommand::DeregisterArbiter(arb_id)); .unbounded_send(SystemCommand::UnregisterArbiter(id));
}
}) })
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
panic!("Cannot spawn Arbiter's thread: {:?}. {:?}", &name, err) panic!("Cannot spawn an arbiter's thread {:?}: {:?}", &name, err)
}); });
ready_rx.recv().unwrap(); Arbiter {
sender: arb_tx2,
Arbiter { tx, thread_handle } thread_handle: Some(handle),
}
} }
/// Sets up an Arbiter runner in a new System using the provided runtime local task set. pub(crate) fn run_system(rt: Option<&Runtime>) {
pub(crate) fn in_new_system(local: &LocalSet) -> ArbiterHandle { RUNNING.with(|cell| cell.set(true));
let (tx, rx) = mpsc::unbounded_channel(); Q.with(|cell| {
let mut v = cell.borrow_mut();
let hnd = ArbiterHandle::new(tx); for fut in v.drain(..) {
if let Some(rt) = rt {
HANDLE.with(|cell| *cell.borrow_mut() = Some(hnd.clone())); rt.spawn(fut);
} else {
local.spawn_local(ArbiterRunner { rx }); tokio::task::spawn_local(fut);
}
hnd }
});
} }
/// Return a handle to the this Arbiter's message sender. pub(crate) fn stop_system() {
pub fn handle(&self) -> ArbiterHandle { RUNNING.with(|cell| cell.set(false));
ArbiterHandle::new(self.tx.clone())
} }
/// Return a handle to the current thread's Arbiter's message sender. /// Spawn a future on the current thread. This does not create a new Arbiter
/// /// or Arbiter address, it is simply a helper for spawning futures on the current
/// # Panics /// thread.
/// Panics if no Arbiter is running on the current thread. pub fn spawn<F>(future: F)
pub fn current() -> ArbiterHandle {
HANDLE.with(|cell| match *cell.borrow() {
Some(ref hnd) => hnd.clone(),
None => panic!("Arbiter is not running."),
})
}
/// Stop Arbiter from continuing it's event loop.
///
/// Returns true if stop message was sent successfully and false if the Arbiter has been dropped.
pub fn stop(&self) -> bool {
self.tx.send(ArbiterCommand::Stop).is_ok()
}
/// Send a future to the Arbiter's thread and spawn it.
///
/// 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.
pub fn spawn<Fut>(&self, future: Fut) -> bool
where where
Fut: Future<Output = ()> + Send + 'static, F: Future<Output = ()> + 'static,
{ {
self.tx RUNNING.with(move |cell| {
.send(ArbiterCommand::Execute(Box::pin(future))) if cell.get() {
.is_ok() // Spawn the future on running executor
let len = PENDING.with(move |cell| {
let mut p = cell.borrow_mut();
p.push(tokio::task::spawn_local(future));
p.len()
});
if len > 7 {
// Before reaching the inline size
tokio::task::spawn_local(CleanupPending);
}
} else {
// Box the future and push it to the queue, this results in double boxing
// because the executor boxes the future again, but works for now
Q.with(move |cell| {
cell.borrow_mut().push(Pin::from(Box::alloc().init(future)))
});
}
});
} }
/// Send a function to the Arbiter's thread and execute it. /// Executes a future on the current thread. This does not create a new Arbiter
/// /// or Arbiter address, it is simply a helper for executing futures on the current
/// Any result from the function is discarded. If you require a result, include a response /// thread.
/// channel in the function. pub fn spawn_fn<F, R>(f: F)
/// where
/// Returns true if function was sent successfully and false if the Arbiter has died. F: FnOnce() -> R + 'static,
pub fn spawn_fn<F>(&self, f: F) -> bool R: Future<Output = ()> + 'static,
{
Arbiter::spawn(future::lazy(|_| f()).flatten())
}
/// Send a future to the Arbiter's thread, and spawn it.
pub fn send<F>(&self, future: F)
where
F: Future<Output = ()> + Send + Unpin + 'static,
{
let _ = self
.sender
.unbounded_send(ArbiterCommand::Execute(Box::new(future)));
}
/// Send a function to the Arbiter's thread, and execute it. Any result from the function
/// is discarded.
pub fn exec_fn<F>(&self, f: F)
where where
F: FnOnce() + Send + 'static, F: FnOnce() + Send + 'static,
{ {
self.spawn(async { f() }) let _ = self
.sender
.unbounded_send(ArbiterCommand::ExecuteFn(Box::new(move || {
f();
})));
} }
/// Wait for Arbiter's event loop to complete. /// Send a function to the Arbiter's thread. This function will be executed asynchronously.
/// A future is created, and when resolved will contain the result of the function sent
/// to the Arbiters thread.
pub fn exec<F, R>(&self, f: F) -> impl Future<Output = Result<R, Canceled>>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
let (tx, rx) = channel();
let _ = self
.sender
.unbounded_send(ArbiterCommand::ExecuteFn(Box::new(move || {
if !tx.is_canceled() {
let _ = tx.send(f());
}
})));
rx
}
/// Set item to arbiter storage
pub fn set_item<T: 'static>(item: T) {
STORAGE.with(move |cell| cell.borrow_mut().insert(TypeId::of::<T>(), Box::new(item)));
}
/// Check if arbiter storage contains item
pub fn contains_item<T: 'static>() -> bool {
STORAGE.with(move |cell| cell.borrow().get(&TypeId::of::<T>()).is_some())
}
/// Get a reference to a type previously inserted on this arbiter's storage.
/// ///
/// Joins the underlying OS thread handle. See [`JoinHandle::join`](thread::JoinHandle::join). /// Panics is item is not inserted
pub fn join(self) -> thread::Result<()> { pub fn get_item<T: 'static, F, R>(mut f: F) -> R
self.thread_handle.join() where
F: FnMut(&T) -> R,
{
STORAGE.with(move |cell| {
let st = cell.borrow();
let item = st
.get(&TypeId::of::<T>())
.and_then(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref())
.unwrap();
f(item)
})
}
/// Get a mutable reference to a type previously inserted on this arbiter's storage.
///
/// Panics is item is not inserted
pub fn get_mut_item<T: 'static, F, R>(mut f: F) -> R
where
F: FnMut(&mut T) -> R,
{
STORAGE.with(move |cell| {
let mut st = cell.borrow_mut();
let item = st
.get_mut(&TypeId::of::<T>())
.and_then(|boxed| (&mut **boxed as &mut (dyn Any + 'static)).downcast_mut())
.unwrap();
f(item)
})
}
fn with_sender(sender: UnboundedSender<ArbiterCommand>) -> Self {
Self {
sender,
thread_handle: None,
}
}
/// Wait for the event loop to stop by joining the underlying thread (if have Some).
pub fn join(&mut self) -> thread::Result<()> {
if let Some(thread_handle) = self.thread_handle.take() {
thread_handle.join()
} else {
Ok(())
}
}
/// Returns a future that will be completed once all currently spawned futures
/// have completed.
pub fn local_join() -> impl Future<Output = ()> {
PENDING.with(move |cell| {
let current = cell.replace(SmallVec::new());
future::join_all(current).map(|_| ())
})
} }
} }
/// A persistent future that processes [Arbiter] commands. /// Future used for cleaning-up already finished `JoinHandle`s
struct ArbiterRunner { /// from the `PENDING` list so the vector doesn't grow indefinitely
rx: mpsc::UnboundedReceiver<ArbiterCommand>, struct CleanupPending;
}
impl Future for ArbiterRunner { impl Future for CleanupPending {
type Output = (); type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// process all items currently buffered in channel PENDING.with(move |cell| {
loop { let mut pending = cell.borrow_mut();
match ready!(Pin::new(&mut self.rx).poll_recv(cx)) { let mut i = 0;
// channel closed; no more messages can be received while i != pending.len() {
None => return Poll::Ready(()), if Pin::new(&mut pending[i]).poll(cx).is_ready() {
pending.remove(i);
} else {
i += 1;
}
}
});
// process arbiter command Poll::Ready(())
Some(item) => match item { }
ArbiterCommand::Stop => { }
return Poll::Ready(());
} struct ArbiterController {
ArbiterCommand::Execute(task_fut) => { stop: Option<Sender<i32>>,
tokio::task::spawn_local(task_fut); rx: UnboundedReceiver<ArbiterCommand>,
} }
},
impl Drop for ArbiterController {
fn drop(&mut self) {
if thread::panicking() {
if System::current().stop_on_panic() {
eprintln!("Panic in Arbiter thread, shutting down system.");
System::current().stop_with_code(1)
} else {
eprintln!("Panic in Arbiter thread.");
} }
} }
} }
} }
impl Future for ArbiterController {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
loop {
match Pin::new(&mut self.rx).poll_next(cx) {
Poll::Ready(None) => return Poll::Ready(()),
Poll::Ready(Some(item)) => match item {
ArbiterCommand::Stop => {
if let Some(stop) = self.stop.take() {
let _ = stop.send(0);
};
return Poll::Ready(());
}
ArbiterCommand::Execute(fut) => {
let len = PENDING.with(move |cell| {
let mut p = cell.borrow_mut();
p.push(tokio::task::spawn_local(fut));
p.len()
});
if len > 7 {
// Before reaching the inline size
tokio::task::spawn_local(CleanupPending);
}
}
ArbiterCommand::ExecuteFn(f) => {
f.call_box();
}
},
Poll::Pending => return Poll::Pending,
}
}
}
}
#[derive(Debug)]
pub(crate) enum SystemCommand {
Exit(i32),
RegisterArbiter(usize, Arbiter),
UnregisterArbiter(usize),
}
#[derive(Debug)]
pub(crate) struct SystemArbiter {
stop: Option<Sender<i32>>,
commands: UnboundedReceiver<SystemCommand>,
arbiters: HashMap<usize, Arbiter>,
}
impl SystemArbiter {
pub(crate) fn new(stop: Sender<i32>, commands: UnboundedReceiver<SystemCommand>) -> Self {
SystemArbiter {
commands,
stop: Some(stop),
arbiters: HashMap::new(),
}
}
}
impl Future for SystemArbiter {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
loop {
match Pin::new(&mut self.commands).poll_next(cx) {
Poll::Ready(None) => return Poll::Ready(()),
Poll::Ready(Some(cmd)) => match cmd {
SystemCommand::Exit(code) => {
// stop arbiters
for arb in self.arbiters.values() {
arb.stop();
}
// stop event loop
if let Some(stop) = self.stop.take() {
let _ = stop.send(code);
}
}
SystemCommand::RegisterArbiter(name, hnd) => {
self.arbiters.insert(name, hnd);
}
SystemCommand::UnregisterArbiter(name) => {
self.arbiters.remove(&name);
}
},
Poll::Pending => return Poll::Pending,
}
}
}
}
pub trait FnExec: Send + 'static {
fn call_box(self: Box<Self>);
}
impl<F> FnExec for F
where
F: FnOnce() + Send + 'static,
{
#[allow(clippy::boxed_local)]
fn call_box(self: Box<Self>) {
(*self)()
}
}

191
actix-rt/src/builder.rs Normal file
View File

@@ -0,0 +1,191 @@
use std::borrow::Cow;
use std::io;
use futures_channel::mpsc::unbounded;
use futures_channel::oneshot::{channel, Receiver};
use futures_util::future::{lazy, Future, FutureExt};
use tokio::task::LocalSet;
use crate::arbiter::{Arbiter, SystemArbiter};
use crate::runtime::Runtime;
use crate::system::System;
/// Builder struct for a actix runtime.
///
/// Either use `Builder::build` to create a system and start actors.
/// Alternatively, use `Builder::run` to start the tokio runtime and
/// run a function in its context.
pub struct Builder {
/// Name of the System. Defaults to "actix" if unset.
name: Cow<'static, str>,
/// Whether the Arbiter will stop the whole System on uncaught panic. Defaults to false.
stop_on_panic: bool,
}
impl Builder {
pub(crate) fn new() -> Self {
Builder {
name: Cow::Borrowed("actix"),
stop_on_panic: false,
}
}
/// Sets the name of the System.
pub fn name<T: Into<String>>(mut self, name: T) -> Self {
self.name = Cow::Owned(name.into());
self
}
/// Sets the option 'stop_on_panic' which controls whether the System is stopped when an
/// uncaught panic is thrown from a worker thread.
///
/// Defaults to false.
pub fn stop_on_panic(mut self, stop_on_panic: bool) -> Self {
self.stop_on_panic = stop_on_panic;
self
}
/// Create new System.
///
/// This method panics if it can not create tokio runtime
pub fn build(self) -> SystemRunner {
self.create_runtime(|| {})
}
/// Create new System that can run asynchronously.
///
/// This method panics if it cannot start the system arbiter
pub(crate) fn build_async(self, local: &LocalSet) -> AsyncSystemRunner {
self.create_async_runtime(local)
}
/// This function will start tokio runtime and will finish once the
/// `System::stop()` message get called.
/// Function `f` get called within tokio runtime context.
pub fn run<F>(self, f: F) -> io::Result<()>
where
F: FnOnce() + 'static,
{
self.create_runtime(f).run()
}
fn create_async_runtime(self, local: &LocalSet) -> AsyncSystemRunner {
let (stop_tx, stop) = channel();
let (sys_sender, sys_receiver) = unbounded();
let system = System::construct(sys_sender, Arbiter::new_system(), self.stop_on_panic);
// system arbiter
let arb = SystemArbiter::new(stop_tx, sys_receiver);
// start the system arbiter
let _ = local.spawn_local(arb);
AsyncSystemRunner { stop, system }
}
fn create_runtime<F>(self, f: F) -> SystemRunner
where
F: FnOnce() + 'static,
{
let (stop_tx, stop) = channel();
let (sys_sender, sys_receiver) = unbounded();
let system = System::construct(sys_sender, Arbiter::new_system(), self.stop_on_panic);
// system arbiter
let arb = SystemArbiter::new(stop_tx, sys_receiver);
let mut rt = Runtime::new().unwrap();
rt.spawn(arb);
// init system arbiter and run configuration method
rt.block_on(lazy(move |_| f()));
SystemRunner { rt, stop, system }
}
}
#[derive(Debug)]
pub(crate) struct AsyncSystemRunner {
stop: Receiver<i32>,
system: System,
}
impl AsyncSystemRunner {
/// This function will start event loop and returns a future that
/// resolves once the `System::stop()` function is called.
pub(crate) fn run_nonblocking(self) -> impl Future<Output = Result<(), io::Error>> + Send {
let AsyncSystemRunner { stop, .. } = self;
// run loop
lazy(|_| {
Arbiter::run_system(None);
async {
let res = match stop.await {
Ok(code) => {
if code != 0 {
Err(io::Error::new(
io::ErrorKind::Other,
format!("Non-zero exit code: {}", code),
))
} else {
Ok(())
}
}
Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
};
Arbiter::stop_system();
res
}
})
.flatten()
}
}
/// Helper object that runs System's event loop
#[must_use = "SystemRunner must be run"]
#[derive(Debug)]
pub struct SystemRunner {
rt: Runtime,
stop: Receiver<i32>,
system: System,
}
impl SystemRunner {
/// This function will start event loop and will finish once the
/// `System::stop()` function is called.
pub fn run(self) -> io::Result<()> {
let SystemRunner { mut rt, stop, .. } = self;
// run loop
Arbiter::run_system(Some(&rt));
let result = match rt.block_on(stop) {
Ok(code) => {
if code != 0 {
Err(io::Error::new(
io::ErrorKind::Other,
format!("Non-zero exit code: {}", code),
))
} else {
Ok(())
}
}
Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
};
Arbiter::stop_system();
result
}
/// Execute a future and wait for result.
pub fn block_on<F, O>(&mut self, fut: F) -> O
where
F: Future<Output = O> + 'static,
{
Arbiter::run_system(Some(&self.rt));
let res = self.rt.block_on(fut);
Arbiter::stop_system();
res
}
}

View File

@@ -1,168 +1,65 @@
//! Tokio-based single-threaded async runtime for the Actix ecosystem. //! A runtime implementation that runs everything on the current thread.
//!
//! In most parts of the the Actix ecosystem, it has been chosen to use !Send futures. For this
//! reason, a single-threaded runtime is appropriate since it is guaranteed that futures will not
//! be moved between threads. This can result in small performance improvements over cases where
//! atomics would otherwise be needed.
//!
//! To achieve similar performance to multi-threaded, work-stealing runtimes, applications
//! using `actix-rt` will create multiple, mostly disconnected, single-threaded runtimes.
//! This approach has good performance characteristics for workloads where the majority of tasks
//! have similar runtime expense.
//!
//! The disadvantage is that idle threads will not steal work from very busy, stuck or otherwise
//! backlogged threads. Tasks that are disproportionately expensive should be offloaded to the
//! blocking task thread-pool using [`task::spawn_blocking`].
//!
//! # Examples
//! ```
//! use std::sync::mpsc;
//! use actix_rt::{Arbiter, System};
//!
//! let _ = System::new();
//!
//! let (tx, rx) = mpsc::channel::<u32>();
//!
//! let arbiter = Arbiter::new();
//! arbiter.spawn_fn(move || tx.send(42).unwrap());
//!
//! let num = rx.recv().unwrap();
//! assert_eq!(num, 42);
//!
//! arbiter.stop();
//! arbiter.join().unwrap();
//! ```
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
#![warn(missing_docs)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
use std::future::Future; #[cfg(not(test))] // Work around for rust-lang/rust#62127
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, test}; pub use actix_macros::{main, test};
mod arbiter; mod arbiter;
mod builder;
mod runtime; mod runtime;
mod system; mod system;
pub use self::arbiter::{Arbiter, ArbiterHandle}; pub use self::arbiter::Arbiter;
pub use self::builder::{Builder, SystemRunner};
pub use self::runtime::Runtime; pub use self::runtime::Runtime;
pub use self::system::{System, SystemRunner}; pub use self::system::System;
pub use tokio::pin; /// Spawns a future on the current arbiter.
///
/// # Panics
///
/// This function panics if actix system is not running.
pub fn spawn<F>(f: F)
where
F: futures_util::future::Future<Output = ()> + 'static,
{
if !System::is_set() {
panic!("System is not running");
}
Arbiter::spawn(f);
}
/// Asynchronous signal handling
pub mod signal { pub mod signal {
//! Asynchronous signal handling (Tokio re-exports).
#[cfg(unix)] #[cfg(unix)]
pub mod unix { pub mod unix {
//! Unix specific signals (Tokio re-exports).
pub use tokio::signal::unix::*; pub use tokio::signal::unix::*;
} }
pub use tokio::signal::ctrl_c; pub use tokio::signal::ctrl_c;
} }
/// TCP/UDP/Unix bindings
pub mod net { pub mod net {
//! TCP/UDP/Unix bindings (mostly Tokio re-exports).
use std::{
future::Future,
io,
task::{Context, Poll},
};
pub use tokio::io::Ready;
use tokio::io::{AsyncRead, AsyncWrite, Interest};
pub use tokio::net::UdpSocket; pub use tokio::net::UdpSocket;
pub use tokio::net::{TcpListener, TcpSocket, TcpStream}; pub use tokio::net::{TcpListener, TcpStream};
#[cfg(unix)] #[cfg(unix)]
pub use tokio::net::{UnixDatagram, UnixListener, UnixStream}; mod unix {
pub use tokio::net::{UnixDatagram, UnixListener, UnixStream};
/// Extension trait over async read+write types that can also signal readiness.
#[doc(hidden)]
pub trait ActixStream: AsyncRead + AsyncWrite + Unpin {
/// Poll stream and check read readiness of Self.
///
/// See [tokio::net::TcpStream::poll_read_ready] for detail on intended use.
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>>;
/// Poll stream and check write readiness of Self.
///
/// See [tokio::net::TcpStream::poll_write_ready] for detail on intended use.
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>>;
}
impl ActixStream for TcpStream {
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
let ready = self.ready(Interest::READABLE);
tokio::pin!(ready);
ready.poll(cx)
}
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
let ready = self.ready(Interest::WRITABLE);
tokio::pin!(ready);
ready.poll(cx)
}
} }
#[cfg(unix)] #[cfg(unix)]
impl ActixStream for UnixStream { pub use self::unix::*;
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
let ready = self.ready(Interest::READABLE);
tokio::pin!(ready);
ready.poll(cx)
}
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
let ready = self.ready(Interest::WRITABLE);
tokio::pin!(ready);
ready.poll(cx)
}
}
impl<Io: ActixStream + ?Sized> ActixStream for Box<Io> {
fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
(**self).poll_read_ready(cx)
}
fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<io::Result<Ready>> {
(**self).poll_write_ready(cx)
}
}
} }
/// Utilities for tracking time.
pub mod time { pub mod time {
//! Utilities for tracking time (Tokio re-exports).
pub use tokio::time::Instant; pub use tokio::time::Instant;
pub use tokio::time::{delay_for, delay_until, Delay};
pub use tokio::time::{interval, interval_at, Interval}; 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::{timeout, Timeout};
} }
pub mod task {
//! Task management (Tokio re-exports).
pub use tokio::task::{spawn_blocking, yield_now, JoinError, JoinHandle};
}
/// Spawns a future on the current thread.
///
/// # Panics
/// Panics if Actix system is not running.
#[inline]
pub fn spawn<Fut>(f: Fut) -> JoinHandle<()>
where
Fut: Future<Output = ()> + 'static,
{
tokio::task::spawn_local(f)
}

View File

@@ -1,29 +1,28 @@
use std::{future::Future, io}; use std::future::Future;
use std::io;
use tokio::{runtime, task::LocalSet};
use tokio::task::{JoinHandle, LocalSet}; /// Single-threaded runtime provides a way to start reactor
/// and runtime on the current thread.
/// A Tokio-based runtime proxy.
/// ///
/// All spawned futures will be executed on the current thread. Therefore, there is no `Send` bound /// See [module level][mod] documentation for more details.
/// on submitted futures. ///
/// [mod]: crate
#[derive(Debug)] #[derive(Debug)]
pub struct Runtime { pub struct Runtime {
local: LocalSet, local: LocalSet,
rt: tokio::runtime::Runtime, rt: runtime::Runtime,
}
pub(crate) fn default_tokio_runtime() -> io::Result<tokio::runtime::Runtime> {
tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
.build()
} }
impl Runtime { impl Runtime {
/// Returns a new runtime initialized with default configuration values.
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self)]
pub fn new() -> io::Result<Self> { /// Returns a new runtime initialized with default configuration values.
let rt = default_tokio_runtime()?; pub fn new() -> io::Result<Runtime> {
let rt = runtime::Builder::new()
.enable_io()
.enable_time()
.basic_scheduler()
.build()?;
Ok(Runtime { Ok(Runtime {
rt, rt,
@@ -31,66 +30,62 @@ impl Runtime {
}) })
} }
/// Reference to local task set. /// Spawn a future onto the single-threaded runtime.
pub(crate) fn local_set(&self) -> &LocalSet {
&self.local
}
/// Offload a future onto the single-threaded runtime.
/// ///
/// The returned join handle can be used to await the future's result. /// See [module level][mod] documentation for more details.
/// ///
/// See [crate root][crate] documentation for more details. /// [mod]: crate
/// ///
/// # Examples /// # Examples
/// ``` ///
/// let rt = actix_rt::Runtime::new().unwrap(); /// ```rust,ignore
/// # use futures::{future, Future, Stream};
/// use actix_rt::Runtime;
///
/// # fn dox() {
/// // Create the runtime
/// let mut rt = Runtime::new().unwrap();
/// ///
/// // Spawn a future onto the runtime /// // Spawn a future onto the runtime
/// let handle = rt.spawn(async { /// rt.spawn(future::lazy(|_| {
/// println!("running on the runtime"); /// println!("running on the runtime");
/// 42 /// }));
/// }); /// # }
/// /// # pub fn main() {}
/// assert_eq!(rt.block_on(handle).unwrap(), 42);
/// ``` /// ```
/// ///
/// # Panics /// # 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. /// This function panics if the spawn fails. Failure occurs if the executor
pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output> /// is currently at capacity and is unable to spawn a new future.
pub fn spawn<F>(&self, future: F) -> &Self
where
F: Future<Output = ()> + 'static,
{
self.local.spawn_local(future);
self
}
/// 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 `future` has resolved either successfully or with an
/// error. The result of the future is then returned from this function
/// call.
///
/// Note that this function will **also** execute any spawned futures on the
/// current thread, but will **not** block until these other spawned futures
/// have completed. Once the function returns, any uncompleted futures
/// remain pending in the `Runtime` instance. These futures will not run
/// until `block_on` or `run` is called again.
///
/// The caller is responsible for ensuring that other spawned futures
/// complete execution by calling `block_on` or `run`.
pub fn block_on<F>(&mut self, f: F) -> F::Output
where where
F: Future + 'static, F: Future + 'static,
{ {
self.local.spawn_local(future) self.local.block_on(&mut self.rt, f)
}
/// 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
/// `future` has resolved either successfully or with an error. The result of the future is
/// then returned from this function call.
///
/// Note that this function will also execute any spawned futures on the current thread, but
/// will not block until these other spawned futures have completed. Once the function returns,
/// any uncompleted futures remain pending in the `Runtime` instance. These futures will not run
/// until `block_on` or `run` is called again.
///
/// The caller is responsible for ensuring that other spawned futures complete execution by
/// calling `block_on` or `run`.
pub fn block_on<F>(&self, f: F) -> F::Output
where
F: Future,
{
self.local.block_on(&self.rt, f)
}
}
impl From<tokio::runtime::Runtime> for Runtime {
fn from(rt: tokio::runtime::Runtime) -> Self {
Self {
local: LocalSet::new(),
rt,
}
} }
} }

View File

@@ -1,98 +1,195 @@
use std::{ use std::cell::RefCell;
cell::RefCell, use std::future::Future;
collections::HashMap, use std::io;
future::Future, use std::sync::atomic::{AtomicUsize, Ordering};
io,
pin::Pin,
sync::atomic::{AtomicUsize, Ordering},
task::{Context, Poll},
};
use futures_core::ready; use futures_channel::mpsc::UnboundedSender;
use tokio::sync::{mpsc, oneshot}; use tokio::task::LocalSet;
use crate::{arbiter::ArbiterHandle, runtime::default_tokio_runtime, Arbiter, Runtime}; use crate::arbiter::{Arbiter, SystemCommand};
use crate::builder::{Builder, SystemRunner};
static SYSTEM_COUNT: AtomicUsize = AtomicUsize::new(0); static SYSTEM_COUNT: AtomicUsize = AtomicUsize::new(0);
/// System is a runtime manager.
#[derive(Clone, Debug)]
pub struct System {
id: usize,
sys: UnboundedSender<SystemCommand>,
arbiter: Arbiter,
stop_on_panic: bool,
}
thread_local!( thread_local!(
static CURRENT: RefCell<Option<System>> = RefCell::new(None); static CURRENT: RefCell<Option<System>> = RefCell::new(None);
); );
/// A manager for a per-thread distributed async runtime.
#[derive(Clone, Debug)]
pub struct System {
id: usize,
sys_tx: mpsc::UnboundedSender<SystemCommand>,
/// Handle to the first [Arbiter] that is created with the System.
arbiter_handle: ArbiterHandle,
}
impl System { impl System {
/// Create a new system. /// Constructs new system and sets it as current
///
/// # Panics
/// Panics if underlying Tokio runtime can not be created.
#[allow(clippy::new_ret_no_self)]
pub fn new() -> SystemRunner {
Self::with_tokio_rt(|| {
default_tokio_runtime()
.expect("Default Actix (Tokio) runtime could not be created.")
})
}
/// Create a new System using the [Tokio Runtime](tokio-runtime) returned from a closure.
///
/// [tokio-runtime]: tokio::runtime::Runtime
#[doc(hidden)]
pub fn with_tokio_rt<F>(runtime_factory: F) -> SystemRunner
where
F: Fn() -> tokio::runtime::Runtime,
{
let (stop_tx, stop_rx) = oneshot::channel();
let (sys_tx, sys_rx) = mpsc::unbounded_channel();
let rt = Runtime::from(runtime_factory());
let sys_arbiter = Arbiter::in_new_system(rt.local_set());
let system = System::construct(sys_tx, sys_arbiter.clone());
system
.tx()
.send(SystemCommand::RegisterArbiter(usize::MAX, sys_arbiter))
.unwrap();
// init background system arbiter
let sys_ctrl = SystemController::new(sys_rx, stop_tx);
rt.spawn(sys_ctrl);
SystemRunner {
rt,
stop_rx,
system,
}
}
/// Constructs new system and registers it on the current thread.
pub(crate) fn construct( pub(crate) fn construct(
sys_tx: mpsc::UnboundedSender<SystemCommand>, sys: UnboundedSender<SystemCommand>,
arbiter_handle: ArbiterHandle, arbiter: Arbiter,
stop_on_panic: bool,
) -> Self { ) -> Self {
let sys = System { let sys = System {
sys_tx, sys,
arbiter_handle, arbiter,
stop_on_panic,
id: SYSTEM_COUNT.fetch_add(1, Ordering::SeqCst), id: SYSTEM_COUNT.fetch_add(1, Ordering::SeqCst),
}; };
System::set_current(sys.clone()); System::set_current(sys.clone());
sys sys
} }
/// Get current running system. /// Build a new system with a customized tokio runtime.
/// ///
/// # Panics /// This allows to customize the runtime. See struct level docs on
/// Panics if no system is registered on the current thread. /// `Builder` for more information.
pub fn builder() -> Builder {
Builder::new()
}
#[allow(clippy::new_ret_no_self)]
/// Create new system.
///
/// This method panics if it can not create tokio runtime
pub fn new<T: Into<String>>(name: T) -> SystemRunner {
Self::builder().name(name).build()
}
/// Create new system using provided tokio `LocalSet`.
///
/// This method panics if it can not spawn system arbiter
///
/// Note: This method uses provided `LocalSet` to create a `System` future only.
/// All the [`Arbiter`]s will be started in separate threads using their own tokio `Runtime`s.
/// It means that using this method currently it is impossible to make `actix-rt` work in the
/// alternative `tokio` `Runtime`s (e.g. provided by [`tokio_compat`]).
///
/// [`tokio_compat`]: https://crates.io/crates/tokio-compat
///
/// # Examples
///
/// ```
/// use tokio::{runtime::Runtime, task::LocalSet};
/// use actix_rt::System;
/// use futures_util::future::try_join_all;
///
/// async fn run_application() {
/// let first_task = tokio::spawn(async {
/// // ...
/// # println!("One task");
/// # Ok::<(),()>(())
/// });
///
/// let second_task = tokio::spawn(async {
/// // ...
/// # println!("Another task");
/// # Ok::<(),()>(())
/// });
///
/// try_join_all(vec![first_task, second_task])
/// .await
/// .expect("Some of the futures finished unexpectedly");
/// }
///
///
/// let mut runtime = tokio::runtime::Builder::new()
/// .core_threads(2)
/// .enable_all()
/// .threaded_scheduler()
/// .build()
/// .unwrap();
///
///
/// let actix_system_task = LocalSet::new();
/// let sys = System::run_in_tokio("actix-main-system", &actix_system_task);
/// actix_system_task.spawn_local(sys);
///
/// let rest_operations = run_application();
/// runtime.block_on(actix_system_task.run_until(rest_operations));
/// ```
pub fn run_in_tokio<T: Into<String>>(
name: T,
local: &LocalSet,
) -> impl Future<Output = io::Result<()>> {
Self::builder()
.name(name)
.build_async(local)
.run_nonblocking()
}
/// Consume the provided tokio Runtime and start the `System` in it.
/// This method will create a `LocalSet` object and occupy the current thread
/// for the created `System` exclusively. All the other asynchronous tasks that
/// should be executed as well must be aggregated into one future, provided as the last
/// argument to this method.
///
/// Note: This method uses provided `Runtime` to create a `System` future only.
/// All the [`Arbiter`]s will be started in separate threads using their own tokio `Runtime`s.
/// It means that using this method currently it is impossible to make `actix-rt` work in the
/// alternative `tokio` `Runtime`s (e.g. provided by `tokio_compat`).
///
/// [`tokio_compat`]: https://crates.io/crates/tokio-compat
///
/// # Arguments
///
/// - `name`: Name of the System
/// - `runtime`: A tokio Runtime to run the system in.
/// - `rest_operations`: A future to be executed in the runtime along with the System.
///
/// # Examples
///
/// ```
/// use tokio::runtime::Runtime;
/// use actix_rt::System;
/// use futures_util::future::try_join_all;
///
/// async fn run_application() {
/// let first_task = tokio::spawn(async {
/// // ...
/// # println!("One task");
/// # Ok::<(),()>(())
/// });
///
/// let second_task = tokio::spawn(async {
/// // ...
/// # println!("Another task");
/// # Ok::<(),()>(())
/// });
///
/// try_join_all(vec![first_task, second_task])
/// .await
/// .expect("Some of the futures finished unexpectedly");
/// }
///
///
/// let runtime = tokio::runtime::Builder::new()
/// .core_threads(2)
/// .enable_all()
/// .threaded_scheduler()
/// .build()
/// .unwrap();
///
/// let rest_operations = run_application();
/// System::attach_to_tokio("actix-main-system", runtime, rest_operations);
/// ```
pub fn attach_to_tokio<Fut, R>(
name: impl Into<String>,
mut runtime: tokio::runtime::Runtime,
rest_operations: Fut,
) -> R
where
Fut: std::future::Future<Output = R>,
{
let actix_system_task = LocalSet::new();
let sys = System::run_in_tokio(name.into(), &actix_system_task);
actix_system_task.spawn_local(sys);
runtime.block_on(actix_system_task.run_until(rest_operations))
}
/// Get current running system.
pub fn current() -> System { pub fn current() -> System {
CURRENT.with(|cell| match *cell.borrow() { CURRENT.with(|cell| match *cell.borrow() {
Some(ref sys) => sys.clone(), Some(ref sys) => sys.clone(),
@@ -100,156 +197,67 @@ impl System {
}) })
} }
/// Try to get current running system. /// Check if current system is set, i.e., as already been started.
/// pub fn is_set() -> bool {
/// Returns `None` if no System has been started. CURRENT.with(|cell| cell.borrow().is_some())
///
/// Contrary to `current`, this never panics.
pub fn try_current() -> Option<System> {
CURRENT.with(|cell| cell.borrow().clone())
} }
/// Get handle to a the System's initial [Arbiter]. /// Set current running system.
pub fn arbiter(&self) -> &ArbiterHandle {
&self.arbiter_handle
}
/// Check if there is a System registered on the current thread.
pub fn is_registered() -> bool {
CURRENT.with(|sys| sys.borrow().is_some())
}
/// Register given system on current thread.
#[doc(hidden)] #[doc(hidden)]
pub fn set_current(sys: System) { pub fn set_current(sys: System) {
CURRENT.with(|cell| { CURRENT.with(|s| {
*cell.borrow_mut() = Some(sys); *s.borrow_mut() = Some(sys);
}) })
} }
/// Numeric system identifier. /// Execute function with system reference.
/// pub fn with_current<F, R>(f: F) -> R
/// Useful when using multiple Systems. where
F: FnOnce(&System) -> R,
{
CURRENT.with(|cell| match *cell.borrow() {
Some(ref sys) => f(sys),
None => panic!("System is not running"),
})
}
/// System id
pub fn id(&self) -> usize { pub fn id(&self) -> usize {
self.id self.id
} }
/// Stop the system (with code 0). /// Stop the system
pub fn stop(&self) { pub fn stop(&self) {
self.stop_with_code(0) self.stop_with_code(0)
} }
/// Stop the system with a given exit code. /// Stop the system with a particular exit code.
pub fn stop_with_code(&self, code: i32) { pub fn stop_with_code(&self, code: i32) {
let _ = self.sys_tx.send(SystemCommand::Exit(code)); let _ = self.sys.unbounded_send(SystemCommand::Exit(code));
} }
pub(crate) fn tx(&self) -> &mpsc::UnboundedSender<SystemCommand> { pub(crate) fn sys(&self) -> &UnboundedSender<SystemCommand> {
&self.sys_tx &self.sys
}
}
/// Runner that keeps a [System]'s event loop alive until stop message is received.
#[must_use = "A SystemRunner does nothing unless `run` is called."]
#[derive(Debug)]
pub struct SystemRunner {
rt: Runtime,
stop_rx: oneshot::Receiver<i32>,
system: System,
}
impl SystemRunner {
/// Starts event loop and will return once [System] is [stopped](System::stop).
pub fn run(self) -> io::Result<()> {
let SystemRunner { rt, stop_rx, .. } = self;
// run loop
match rt.block_on(stop_rx) {
Ok(code) => {
if code != 0 {
Err(io::Error::new(
io::ErrorKind::Other,
format!("Non-zero exit code: {}", code),
))
} else {
Ok(())
}
}
Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
}
} }
/// Runs the provided future, blocking the current thread until the future completes. /// Return status of 'stop_on_panic' option which controls whether the System is stopped when an
#[inline] /// uncaught panic is thrown from a worker thread.
pub fn block_on<F: Future>(&self, fut: F) -> F::Output { pub fn stop_on_panic(&self) -> bool {
self.rt.block_on(fut) self.stop_on_panic
} }
}
/// System arbiter
#[derive(Debug)] pub fn arbiter(&self) -> &Arbiter {
pub(crate) enum SystemCommand { &self.arbiter
Exit(i32), }
RegisterArbiter(usize, ArbiterHandle),
DeregisterArbiter(usize), /// This function will start tokio runtime and will finish once the
} /// `System::stop()` message get called.
/// Function `f` get called within tokio runtime context.
/// There is one `SystemController` per [System]. It runs in the background, keeping track of pub fn run<F>(f: F) -> io::Result<()>
/// [Arbiter]s and is able to distribute a system-wide stop command. where
#[derive(Debug)] F: FnOnce() + 'static,
pub(crate) struct SystemController { {
stop_tx: Option<oneshot::Sender<i32>>, Self::builder().run(f)
cmd_rx: mpsc::UnboundedReceiver<SystemCommand>,
arbiters: HashMap<usize, ArbiterHandle>,
}
impl SystemController {
pub(crate) fn new(
cmd_rx: mpsc::UnboundedReceiver<SystemCommand>,
stop_tx: oneshot::Sender<i32>,
) -> Self {
SystemController {
cmd_rx,
stop_tx: Some(stop_tx),
arbiters: HashMap::with_capacity(4),
}
}
}
impl Future for SystemController {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// process all items currently buffered in channel
loop {
match ready!(Pin::new(&mut self.cmd_rx).poll_recv(cx)) {
// channel closed; no more messages can be received
None => return Poll::Ready(()),
// process system command
Some(cmd) => match cmd {
SystemCommand::Exit(code) => {
// stop all arbiters
for arb in self.arbiters.values() {
arb.stop();
}
// stop event loop
// will only fire once
if let Some(stop_tx) = self.stop_tx.take() {
let _ = stop_tx.send(code);
}
}
SystemCommand::RegisterArbiter(id, arb) => {
self.arbiters.insert(id, arb);
}
SystemCommand::DeregisterArbiter(id) => {
self.arbiters.remove(&id);
}
},
}
}
} }
} }

View File

@@ -0,0 +1,114 @@
use std::time::{Duration, Instant};
#[test]
fn start_and_stop() {
actix_rt::System::new("start_and_stop").block_on(async move {
assert!(
actix_rt::Arbiter::is_running(),
"System doesn't seem to have started"
);
});
assert!(
!actix_rt::Arbiter::is_running(),
"System doesn't seem to have stopped"
);
}
#[test]
fn await_for_timer() {
let time = Duration::from_secs(2);
let instant = Instant::now();
actix_rt::System::new("test_wait_timer").block_on(async move {
tokio::time::delay_for(time).await;
});
assert!(
instant.elapsed() >= time,
"Block on should poll awaited future to completion"
);
}
#[test]
fn join_another_arbiter() {
let time = Duration::from_secs(2);
let instant = Instant::now();
actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new();
arbiter.send(Box::pin(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
}));
arbiter.join().unwrap();
});
assert!(
instant.elapsed() >= time,
"Join on another arbiter should complete only when it calls stop"
);
let instant = Instant::now();
actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new();
arbiter.exec_fn(move || {
actix_rt::spawn(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
});
});
arbiter.join().unwrap();
});
assert!(
instant.elapsed() >= time,
"Join on a arbiter that has used actix_rt::spawn should wait for said future"
);
let instant = Instant::now();
actix_rt::System::new("test_join_another_arbiter").block_on(async move {
let mut arbiter = actix_rt::Arbiter::new();
arbiter.send(Box::pin(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
}));
arbiter.stop();
arbiter.join().unwrap();
});
assert!(
instant.elapsed() < time,
"Premature stop of arbiter should conclude regardless of it's current state"
);
}
#[test]
fn join_current_arbiter() {
let time = Duration::from_secs(2);
let instant = Instant::now();
actix_rt::System::new("test_join_current_arbiter").block_on(async move {
actix_rt::spawn(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
});
actix_rt::Arbiter::local_join().await;
});
assert!(
instant.elapsed() >= time,
"Join on current arbiter should wait for all spawned futures"
);
let large_timer = Duration::from_secs(20);
let instant = Instant::now();
actix_rt::System::new("test_join_current_arbiter").block_on(async move {
actix_rt::spawn(async move {
tokio::time::delay_for(time).await;
actix_rt::Arbiter::current().stop();
});
let f = actix_rt::Arbiter::local_join();
actix_rt::spawn(async move {
tokio::time::delay_for(large_timer).await;
actix_rt::Arbiter::current().stop();
});
f.await;
});
assert!(
instant.elapsed() < large_timer,
"local_join should await only for the already spawned futures"
);
}

View File

@@ -1,300 +0,0 @@
use std::{
sync::{
atomic::{AtomicBool, Ordering},
mpsc::channel,
Arc,
},
thread,
time::{Duration, Instant},
};
use actix_rt::{Arbiter, System};
use tokio::sync::oneshot;
#[test]
fn await_for_timer() {
let time = Duration::from_secs(1);
let instant = Instant::now();
System::new().block_on(async move {
tokio::time::sleep(time).await;
});
assert!(
instant.elapsed() >= time,
"Block on should poll awaited future to completion"
);
}
#[test]
fn join_another_arbiter() {
let time = Duration::from_secs(1);
let instant = Instant::now();
System::new().block_on(async move {
let arbiter = Arbiter::new();
arbiter.spawn(Box::pin(async move {
tokio::time::sleep(time).await;
Arbiter::current().stop();
}));
arbiter.join().unwrap();
});
assert!(
instant.elapsed() >= time,
"Join on another arbiter should complete only when it calls stop"
);
let instant = Instant::now();
System::new().block_on(async move {
let arbiter = Arbiter::new();
arbiter.spawn_fn(move || {
actix_rt::spawn(async move {
tokio::time::sleep(time).await;
Arbiter::current().stop();
});
});
arbiter.join().unwrap();
});
assert!(
instant.elapsed() >= time,
"Join on an arbiter that has used actix_rt::spawn should wait for said future"
);
let instant = Instant::now();
System::new().block_on(async move {
let arbiter = Arbiter::new();
arbiter.spawn(Box::pin(async move {
tokio::time::sleep(time).await;
Arbiter::current().stop();
}));
arbiter.stop();
arbiter.join().unwrap();
});
assert!(
instant.elapsed() < time,
"Premature stop of arbiter should conclude regardless of it's current state"
);
}
#[test]
fn non_static_block_on() {
let string = String::from("test_str");
let string = string.as_str();
let sys = System::new();
sys.block_on(async {
actix_rt::time::sleep(Duration::from_millis(1)).await;
assert_eq!("test_str", string);
});
let rt = actix_rt::Runtime::new().unwrap();
rt.block_on(async {
actix_rt::time::sleep(Duration::from_millis(1)).await;
assert_eq!("test_str", string);
});
}
#[test]
fn wait_for_spawns() {
let rt = actix_rt::Runtime::new().unwrap();
let handle = rt.spawn(async {
println!("running on the runtime");
// assertion panic is caught at task boundary
assert_eq!(1, 2);
});
assert!(rt.block_on(handle).is_err());
}
#[test]
fn arbiter_spawn_fn_runs() {
let _ = System::new();
let (tx, rx) = channel::<u32>();
let arbiter = Arbiter::new();
arbiter.spawn_fn(move || tx.send(42).unwrap());
let num = rx.recv().unwrap();
assert_eq!(num, 42);
arbiter.stop();
arbiter.join().unwrap();
}
#[test]
fn arbiter_handle_spawn_fn_runs() {
let sys = System::new();
let (tx, rx) = channel::<u32>();
let arbiter = Arbiter::new();
let handle = arbiter.handle();
drop(arbiter);
handle.spawn_fn(move || {
tx.send(42).unwrap();
System::current().stop()
});
let num = rx.recv_timeout(Duration::from_secs(2)).unwrap();
assert_eq!(num, 42);
handle.stop();
sys.run().unwrap();
}
#[test]
fn arbiter_drop_no_panic_fn() {
let _ = System::new();
let arbiter = Arbiter::new();
arbiter.spawn_fn(|| panic!("test"));
arbiter.stop();
arbiter.join().unwrap();
}
#[test]
fn arbiter_drop_no_panic_fut() {
let _ = System::new();
let arbiter = Arbiter::new();
arbiter.spawn(async { panic!("test") });
arbiter.stop();
arbiter.join().unwrap();
}
#[test]
#[should_panic]
fn no_system_current_panic() {
System::current();
}
#[test]
#[should_panic]
fn no_system_arbiter_new_panic() {
Arbiter::new();
}
#[test]
fn system_arbiter_spawn() {
let runner = System::new();
let (tx, rx) = oneshot::channel();
let sys = System::current();
thread::spawn(|| {
// this thread will have no arbiter in it's thread local so call will panic
Arbiter::current();
})
.join()
.unwrap_err();
let thread = thread::spawn(|| {
// this thread will have no arbiter in it's thread local so use the system handle instead
System::set_current(sys);
let sys = System::current();
let arb = sys.arbiter();
arb.spawn(async move {
tx.send(42u32).unwrap();
System::current().stop();
});
});
assert_eq!(runner.block_on(rx).unwrap(), 42);
thread.join().unwrap();
}
#[test]
fn system_stop_stops_arbiters() {
let sys = System::new();
let arb = Arbiter::new();
// arbiter should be alive to receive spawn msg
assert!(Arbiter::current().spawn_fn(|| {}));
assert!(arb.spawn_fn(|| {}));
System::current().stop();
sys.run().unwrap();
// account for slightly slow thread de-spawns (only observed on windows)
thread::sleep(Duration::from_millis(100));
// arbiter should be dead and return false
assert!(!Arbiter::current().spawn_fn(|| {}));
assert!(!arb.spawn_fn(|| {}));
arb.join().unwrap();
}
#[test]
fn new_system_with_tokio() {
let (tx, rx) = channel();
let res = System::with_tokio_rt(move || {
tokio::runtime::Builder::new_multi_thread()
.enable_io()
.enable_time()
.thread_keep_alive(Duration::from_millis(1000))
.worker_threads(2)
.max_blocking_threads(2)
.on_thread_start(|| {})
.on_thread_stop(|| {})
.build()
.unwrap()
})
.block_on(async {
actix_rt::time::sleep(Duration::from_millis(1)).await;
tokio::task::spawn(async move {
tx.send(42).unwrap();
})
.await
.unwrap();
123usize
});
assert_eq!(res, 123);
assert_eq!(rx.recv().unwrap(), 42);
}
#[test]
fn new_arbiter_with_tokio() {
let _ = System::new();
let arb = Arbiter::with_tokio_rt(|| {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
});
let counter = Arc::new(AtomicBool::new(true));
let counter1 = counter.clone();
let did_spawn = arb.spawn(async move {
actix_rt::time::sleep(Duration::from_millis(1)).await;
counter1.store(false, Ordering::SeqCst);
Arbiter::current().stop();
});
assert!(did_spawn);
arb.join().unwrap();
assert_eq!(false, counter.load(Ordering::SeqCst));
}
#[test]
fn try_current_no_system() {
assert!(System::try_current().is_none())
}
#[test]
fn try_current_with_system() {
System::new().block_on(async { assert!(System::try_current().is_some()) });
}

View File

@@ -1,43 +1,11 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2020-xx-xx
* Prevent panic when shutdown_timeout is very large. [f9262db]
[f9262db]: https://github.com/actix/actix-net/commit/f9262db
## 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]
[#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
## 2.0.0-beta.2 - 2021-01-03
* Merge `actix-testing` to `actix-server` as `test_server` mod. [#242]
[#242]: https://github.com/actix/actix-net/pull/242
## 2.0.0-beta.1 - 2020-12-28
* Added explicit info log message on accept queue pause. [#215] * Added explicit info log message on accept queue pause. [#215]
* Prevent double registration of sockets when back-pressure is resolved. [#223] * 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 [#215]: https://github.com/actix/actix-net/pull/215
[#223]: https://github.com/actix/actix-net/pull/223 [#223]: https://github.com/actix/actix-net/pull/223
[#239]: https://github.com/actix/actix-net/pull/239
## 1.0.4 - 2020-09-12 ## 1.0.4 - 2020-09-12

38
actix-server/Cargo.toml Executable file → Normal file
View File

@@ -1,17 +1,15 @@
[package] [package]
name = "actix-server" name = "actix-server"
version = "2.0.0-beta.3" version = "1.0.4"
authors = [ authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
"Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>",
]
description = "General purpose TCP server built for the Actix ecosystem" description = "General purpose TCP server built for the Actix ecosystem"
keywords = ["network", "framework", "async", "futures"] keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-server" documentation = "https://docs.rs/actix-server/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
exclude = [".gitignore", ".cargo/config"]
edition = "2018" edition = "2018"
[lib] [lib]
@@ -22,21 +20,25 @@ path = "src/lib.rs"
default = [] default = []
[dependencies] [dependencies]
actix-codec = "0.4.0-beta.1" actix-service = "1.0.6"
actix-rt = { version = "2.0.0", default-features = false } actix-rt = "1.1.1"
actix-service = "2.0.0-beta.5" actix-codec = "0.3.0"
actix-utils = "3.0.0-beta.2" actix-utils = "2.0.0"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
log = "0.4" log = "0.4"
mio = { version = "0.7.6", features = ["os-poll", "net"] }
num_cpus = "1.13" num_cpus = "1.13"
mio = "0.6.19"
socket2 = "0.3"
futures-channel = { version = "0.3.4", default-features = false }
futures-util = { version = "0.3.4", default-features = false, features = ["sink"] }
slab = "0.4" slab = "0.4"
tokio = { version = "1.2", features = ["sync"] }
# unix domain sockets
# FIXME: Remove it and use mio own uds feature once mio 0.7 is released
mio-uds = { version = "0.6.7" }
[dev-dependencies] [dev-dependencies]
actix-rt = "2.0.0" bytes = "0.5"
bytes = "1" env_logger = "0.7"
env_logger = "0.8" actix-testing = "1.0.0"
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] } tokio = { version = "0.2", features = ["io-util"] }
tokio = { version = "1", features = ["io-util"] }

View File

@@ -1,91 +1,120 @@
use std::sync::mpsc as sync_mpsc;
use std::time::Duration; use std::time::Duration;
use std::{io, thread}; use std::{io, thread};
use actix_rt::{ use actix_rt::time::{delay_until, Instant};
time::{sleep, Instant}, use actix_rt::System;
System,
};
use log::{error, info}; use log::{error, info};
use mio::{Interest, Poll, Token as MioToken};
use slab::Slab; use slab::Slab;
use crate::server::Server; use crate::server::Server;
use crate::socket::{MioListener, SocketAddr}; use crate::socket::{SocketAddr, SocketListener, StdListener};
use crate::waker_queue::{WakerInterest, WakerQueue, WAKER_TOKEN}; use crate::worker::{Conn, WorkerClient};
use crate::worker::{Conn, WorkerHandle};
use crate::Token; use crate::Token;
pub(crate) enum Command {
Pause,
Resume,
Stop,
Worker(WorkerClient),
}
struct ServerSocketInfo { struct ServerSocketInfo {
/// Address of socket. Mainly used for logging.
addr: SocketAddr, addr: SocketAddr,
/// Beware this is the crate token for identify socket and should not be confused
/// with `mio::Token`.
token: Token, token: Token,
sock: SocketListener,
lst: MioListener,
/// Timeout is used to mark the deadline when this socket's listener should be registered again
/// after an error.
timeout: Option<Instant>, timeout: Option<Instant>,
} }
/// Accept loop would live with `ServerBuilder`. #[derive(Clone)]
/// pub(crate) struct AcceptNotify(mio::SetReadiness);
/// It's tasked with construct `Poll` instance and `WakerQueue` which would be distributed to
/// `Accept` and `Worker`. impl AcceptNotify {
/// pub(crate) fn new(ready: mio::SetReadiness) -> Self {
/// It would also listen to `ServerCommand` and push interests to `WakerQueue`. AcceptNotify(ready)
}
pub(crate) fn notify(&self) {
let _ = self.0.set_readiness(mio::Ready::readable());
}
}
impl Default for AcceptNotify {
fn default() -> Self {
AcceptNotify::new(mio::Registration::new2().1)
}
}
pub(crate) struct AcceptLoop { pub(crate) struct AcceptLoop {
cmd_reg: Option<mio::Registration>,
cmd_ready: mio::SetReadiness,
notify_reg: Option<mio::Registration>,
notify_ready: mio::SetReadiness,
tx: sync_mpsc::Sender<Command>,
rx: Option<sync_mpsc::Receiver<Command>>,
srv: Option<Server>, srv: Option<Server>,
poll: Option<Poll>,
waker: WakerQueue,
} }
impl AcceptLoop { impl AcceptLoop {
pub fn new(srv: Server) -> Self { pub fn new(srv: Server) -> AcceptLoop {
let poll = Poll::new().unwrap_or_else(|e| panic!("Can not create `mio::Poll`: {}", e)); let (tx, rx) = sync_mpsc::channel();
let waker = WakerQueue::new(poll.registry()) let (cmd_reg, cmd_ready) = mio::Registration::new2();
.unwrap_or_else(|e| panic!("Can not create `mio::Waker`: {}", e)); let (notify_reg, notify_ready) = mio::Registration::new2();
Self { AcceptLoop {
tx,
cmd_ready,
cmd_reg: Some(cmd_reg),
notify_ready,
notify_reg: Some(notify_reg),
rx: Some(rx),
srv: Some(srv), srv: Some(srv),
poll: Some(poll),
waker,
} }
} }
pub(crate) fn waker_owned(&self) -> WakerQueue { pub fn send(&self, msg: Command) {
self.waker.clone() let _ = self.tx.send(msg);
let _ = self.cmd_ready.set_readiness(mio::Ready::readable());
} }
pub fn wake(&self, i: WakerInterest) { pub fn get_notify(&self) -> AcceptNotify {
self.waker.wake(i); AcceptNotify::new(self.notify_ready.clone())
} }
pub(crate) fn start( pub(crate) fn start(
&mut self, &mut self,
socks: Vec<(Token, MioListener)>, socks: Vec<(Token, StdListener)>,
handles: Vec<WorkerHandle>, workers: Vec<WorkerClient>,
) { ) {
let srv = self.srv.take().expect("Can not re-use AcceptInfo"); let srv = self.srv.take().expect("Can not re-use AcceptInfo");
let poll = self.poll.take().unwrap();
let waker = self.waker.clone();
Accept::start(poll, waker, socks, srv, handles); Accept::start(
self.rx.take().expect("Can not re-use AcceptInfo"),
self.cmd_reg.take().expect("Can not re-use AcceptInfo"),
self.notify_reg.take().expect("Can not re-use AcceptInfo"),
socks,
srv,
workers,
);
} }
} }
/// poll instance of the server.
struct Accept { struct Accept {
poll: Poll, poll: mio::Poll,
waker: WakerQueue, rx: sync_mpsc::Receiver<Command>,
handles: Vec<WorkerHandle>, sockets: Slab<ServerSocketInfo>,
workers: Vec<WorkerClient>,
srv: Server, srv: Server,
timer: (mio::Registration, mio::SetReadiness),
next: usize, next: usize,
backpressure: bool, backpressure: bool,
} }
const DELTA: usize = 100;
const CMD: mio::Token = mio::Token(0);
const TIMER: mio::Token = mio::Token(1);
const NOTIFY: mio::Token = mio::Token(2);
/// This function defines errors that are per-connection. Which basically /// This function defines errors that are per-connection. Which basically
/// means that if we get this error from `accept()` system call it means /// means that if we get this error from `accept()` system call it means
/// next connection might be ready to be accepted. /// next connection might be ready to be accepted.
@@ -100,296 +129,326 @@ fn connection_error(e: &io::Error) -> bool {
} }
impl Accept { impl Accept {
#![allow(clippy::too_many_arguments)]
pub(crate) fn start( pub(crate) fn start(
poll: Poll, rx: sync_mpsc::Receiver<Command>,
waker: WakerQueue, cmd_reg: mio::Registration,
socks: Vec<(Token, MioListener)>, notify_reg: mio::Registration,
socks: Vec<(Token, StdListener)>,
srv: Server, srv: Server,
handles: Vec<WorkerHandle>, workers: Vec<WorkerClient>,
) { ) {
// Accept runs in its own thread and would want to spawn additional futures to current
// actix system.
let sys = System::current(); let sys = System::current();
thread::Builder::new()
// start accept thread
let _ = thread::Builder::new()
.name("actix-server accept loop".to_owned()) .name("actix-server accept loop".to_owned())
.spawn(move || { .spawn(move || {
System::set_current(sys); System::set_current(sys);
let (mut accept, sockets) = let mut accept = Accept::new(rx, socks, workers, srv);
Accept::new_with_sockets(poll, waker, socks, handles, srv);
accept.poll_with(sockets); // Start listening for incoming commands
}) if let Err(err) = accept.poll.register(
.unwrap(); &cmd_reg,
CMD,
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register Registration: {}", err);
}
// Start listening for notify updates
if let Err(err) = accept.poll.register(
&notify_reg,
NOTIFY,
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register Registration: {}", err);
}
accept.poll();
});
} }
fn new_with_sockets( fn new(
poll: Poll, rx: sync_mpsc::Receiver<Command>,
waker: WakerQueue, socks: Vec<(Token, StdListener)>,
socks: Vec<(Token, MioListener)>, workers: Vec<WorkerClient>,
handles: Vec<WorkerHandle>,
srv: Server, srv: Server,
) -> (Accept, Slab<ServerSocketInfo>) { ) -> Accept {
// Create a poll instance
let poll = match mio::Poll::new() {
Ok(poll) => poll,
Err(err) => panic!("Can not create mio::Poll: {}", err),
};
// Start accept
let mut sockets = Slab::new(); let mut sockets = Slab::new();
for (hnd_token, mut lst) in socks.into_iter() { for (hnd_token, lst) in socks.into_iter() {
let addr = lst.local_addr(); let addr = lst.local_addr();
let server = lst.into_listener();
let entry = sockets.vacant_entry(); let entry = sockets.vacant_entry();
let token = entry.key(); let token = entry.key();
// Start listening for incoming connections // Start listening for incoming connections
poll.registry() if let Err(err) = poll.register(
.register(&mut lst, MioToken(token), Interest::READABLE) &server,
.unwrap_or_else(|e| panic!("Can not register io: {}", e)); mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register io: {}", err);
}
entry.insert(ServerSocketInfo { entry.insert(ServerSocketInfo {
addr, addr,
token: hnd_token, token: hnd_token,
lst, sock: server,
timeout: None, timeout: None,
}); });
} }
let accept = Accept { // Timer
let (tm, tmr) = mio::Registration::new2();
if let Err(err) =
poll.register(&tm, TIMER, mio::Ready::readable(), mio::PollOpt::edge())
{
panic!("Can not register Registration: {}", err);
}
Accept {
poll, poll,
waker, rx,
handles, sockets,
workers,
srv, srv,
next: 0, next: 0,
timer: (tm, tmr),
backpressure: false, backpressure: false,
}; }
(accept, sockets)
} }
fn poll_with(&mut self, mut sockets: Slab<ServerSocketInfo>) { fn poll(&mut self) {
// Create storage for events
let mut events = mio::Events::with_capacity(128); let mut events = mio::Events::with_capacity(128);
loop { loop {
if let Err(e) = self.poll.poll(&mut events, None) { if let Err(err) = self.poll.poll(&mut events, None) {
match e.kind() { panic!("Poll error: {}", err);
std::io::ErrorKind::Interrupted => {
continue;
}
_ => {
panic!("Poll error: {}", e);
}
}
} }
for event in events.iter() { for event in events.iter() {
let token = event.token(); let token = event.token();
match token { match token {
// This is a loop because interests for command from previous version was CMD => {
// a loop that would try to drain the command channel. It's yet unknown if !self.process_cmd() {
// if it's necessary/good practice to actively drain the waker queue. return;
WAKER_TOKEN => 'waker: loop {
// take guard with every iteration so no new interest can be added
// until the current task is done.
let mut guard = self.waker.guard();
match guard.pop_front() {
// worker notify it becomes available. we may want to recover
// from backpressure.
Some(WakerInterest::WorkerAvailable) => {
drop(guard);
self.maybe_backpressure(&mut sockets, false);
}
// a new worker thread is made and it's handle would be added
// to Accept
Some(WakerInterest::Worker(handle)) => {
drop(guard);
// maybe we want to recover from a backpressure.
self.maybe_backpressure(&mut sockets, false);
self.handles.push(handle);
}
// got timer interest and it's time to try register socket(s)
// again.
Some(WakerInterest::Timer) => {
drop(guard);
self.process_timer(&mut sockets)
}
Some(WakerInterest::Pause) => {
drop(guard);
sockets.iter_mut().for_each(|(_, info)| {
match self.deregister(info) {
Ok(_) => info!(
"Paused accepting connections on {}",
info.addr
),
Err(e) => {
error!("Can not deregister server socket {}", e)
}
}
});
}
Some(WakerInterest::Resume) => {
drop(guard);
sockets.iter_mut().for_each(|(token, info)| {
self.register_logged(token, info);
});
}
Some(WakerInterest::Stop) => {
return self.deregister_all(&mut sockets);
}
// waker queue is drained
None => {
// Reset the WakerQueue before break so it does not grow infinitely
WakerQueue::reset(&mut guard);
break 'waker;
}
} }
}, }
TIMER => self.process_timer(),
NOTIFY => self.backpressure(false),
_ => { _ => {
let token = usize::from(token); let token = usize::from(token);
self.accept(&mut sockets, token); if token < DELTA {
continue;
}
self.accept(token - DELTA);
} }
} }
} }
} }
} }
fn process_timer(&self, sockets: &mut Slab<ServerSocketInfo>) { fn process_timer(&mut self) {
let now = Instant::now(); let now = Instant::now();
sockets.iter_mut().for_each(|(token, info)| { for (token, info) in self.sockets.iter_mut() {
// only the ServerSocketInfo have an associate timeout value was de registered.
if let Some(inst) = info.timeout.take() { if let Some(inst) = info.timeout.take() {
if now > inst { if now > inst {
self.register_logged(token, info); if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not register server socket {}", err);
} else {
info!("Resume accepting connections on {}", info.addr);
}
} else { } else {
info.timeout = Some(inst); info.timeout = Some(inst);
} }
} }
}); }
}
fn process_cmd(&mut self) -> bool {
loop {
match self.rx.try_recv() {
Ok(cmd) => match cmd {
Command::Pause => {
for (_, info) in self.sockets.iter_mut() {
if let Err(err) = self.poll.deregister(&info.sock) {
error!("Can not deregister server socket {}", err);
} else {
info!("Paused accepting connections on {}", info.addr);
}
}
}
Command::Resume => {
for (token, info) in self.sockets.iter() {
if let Err(err) = self.register(token, info) {
error!("Can not resume socket accept process: {}", err);
} else {
info!(
"Accepting connections on {} has been resumed",
info.addr
);
}
}
}
Command::Stop => {
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
return false;
}
Command::Worker(worker) => {
self.backpressure(false);
self.workers.push(worker);
}
},
Err(err) => match err {
sync_mpsc::TryRecvError::Empty => break,
sync_mpsc::TryRecvError::Disconnected => {
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
return false;
}
},
}
}
true
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
fn register(&self, token: usize, info: &mut ServerSocketInfo) -> io::Result<()> { fn register(&self, token: usize, info: &ServerSocketInfo) -> io::Result<()> {
self.poll self.poll.register(
.registry() &info.sock,
.register(&mut info.lst, MioToken(token), Interest::READABLE) mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
)
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn register(&self, token: usize, info: &mut ServerSocketInfo) -> io::Result<()> { fn register(&self, token: usize, info: &ServerSocketInfo) -> io::Result<()> {
// On windows, calling register without deregister cause an error. // On windows, calling register without deregister cause an error.
// See https://github.com/actix/actix-web/issues/905 // See https://github.com/actix/actix-web/issues/905
// Calling reregister seems to fix the issue. // Calling reregister seems to fix the issue.
self.poll self.poll
.registry() .register(
.register(&mut info.lst, mio::Token(token), Interest::READABLE) &info.sock,
mio::Token(token + DELTA),
mio::Ready::readable(),
mio::PollOpt::edge(),
)
.or_else(|_| { .or_else(|_| {
self.poll.registry().reregister( self.poll.reregister(
&mut info.lst, &info.sock,
mio::Token(token), mio::Token(token + DELTA),
Interest::READABLE, mio::Ready::readable(),
mio::PollOpt::edge(),
) )
}) })
} }
fn register_logged(&self, token: usize, info: &mut ServerSocketInfo) { fn backpressure(&mut self, on: bool) {
match self.register(token, info) {
Ok(_) => info!("Resume accepting connections on {}", info.addr),
Err(e) => error!("Can not register server socket {}", e),
}
}
fn deregister(&self, info: &mut ServerSocketInfo) -> io::Result<()> {
self.poll.registry().deregister(&mut info.lst)
}
fn deregister_all(&self, sockets: &mut Slab<ServerSocketInfo>) {
sockets.iter_mut().for_each(|(_, info)| {
info!("Accepting connections on {} has been paused", info.addr);
let _ = self.deregister(info);
});
}
fn maybe_backpressure(&mut self, sockets: &mut Slab<ServerSocketInfo>, on: bool) {
if self.backpressure { if self.backpressure {
if !on { if !on {
self.backpressure = false; self.backpressure = false;
for (token, info) in sockets.iter_mut() { for (token, info) in self.sockets.iter() {
if info.timeout.is_some() { if info.timeout.is_some() {
// socket will attempt to re-register itself when its timeout completes // socket will attempt to re-register itself when its timeout completes
continue; continue;
} }
self.register_logged(token, info);
if let Err(err) = self.register(token, info) {
error!("Can not resume socket accept process: {}", err);
} else {
info!("Accepting connections on {} has been resumed", info.addr);
}
} }
} }
} else if on { } else if on {
self.backpressure = true; self.backpressure = true;
self.deregister_all(sockets); for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
info!("Accepting connections on {} has been paused", info.addr);
}
} }
} }
fn accept_one(&mut self, sockets: &mut Slab<ServerSocketInfo>, mut msg: Conn) { fn accept_one(&mut self, mut msg: Conn) {
if self.backpressure { if self.backpressure {
while !self.handles.is_empty() { while !self.workers.is_empty() {
match self.handles[self.next].send(msg) { match self.workers[self.next].send(msg) {
Ok(_) => { Ok(_) => (),
self.set_next();
break;
}
Err(tmp) => { Err(tmp) => {
// worker lost contact and could be gone. a message is sent to self.srv.worker_faulted(self.workers[self.next].idx);
// `ServerBuilder` future to notify it a new worker should be made
// after that remove the fault worker
self.srv.worker_faulted(self.handles[self.next].idx);
msg = tmp; msg = tmp;
self.handles.swap_remove(self.next); self.workers.swap_remove(self.next);
if self.handles.is_empty() { if self.workers.is_empty() {
error!("No workers"); error!("No workers");
return; return;
} else if self.handles.len() <= self.next { } else if self.workers.len() <= self.next {
self.next = 0; self.next = 0;
} }
continue; continue;
} }
} }
self.next = (self.next + 1) % self.workers.len();
break;
} }
} else { } else {
let mut idx = 0; let mut idx = 0;
while idx < self.handles.len() { while idx < self.workers.len() {
idx += 1; idx += 1;
if self.handles[self.next].available() { if self.workers[self.next].available() {
match self.handles[self.next].send(msg) { match self.workers[self.next].send(msg) {
Ok(_) => { Ok(_) => {
self.set_next(); self.next = (self.next + 1) % self.workers.len();
return; return;
} }
// worker lost contact and could be gone. a message is sent to
// `ServerBuilder` future to notify it a new worker should be made.
// after that remove the fault worker and enter backpressure if necessary.
Err(tmp) => { Err(tmp) => {
self.srv.worker_faulted(self.handles[self.next].idx); self.srv.worker_faulted(self.workers[self.next].idx);
msg = tmp; msg = tmp;
self.handles.swap_remove(self.next); self.workers.swap_remove(self.next);
if self.handles.is_empty() { if self.workers.is_empty() {
error!("No workers"); error!("No workers");
self.maybe_backpressure(sockets, true); self.backpressure(true);
return; return;
} else if self.handles.len() <= self.next { } else if self.workers.len() <= self.next {
self.next = 0; self.next = 0;
} }
continue; continue;
} }
} }
} }
self.set_next(); self.next = (self.next + 1) % self.workers.len();
} }
// enable backpressure // enable backpressure
self.maybe_backpressure(sockets, true); self.backpressure(true);
self.accept_one(sockets, msg); self.accept_one(msg);
} }
} }
// set next worker handle that would accept work. fn accept(&mut self, token: usize) {
fn set_next(&mut self) {
self.next = (self.next + 1) % self.handles.len();
}
fn accept(&mut self, sockets: &mut Slab<ServerSocketInfo>, token: usize) {
loop { loop {
let msg = if let Some(info) = sockets.get_mut(token) { let msg = if let Some(info) = self.sockets.get_mut(token) {
match info.lst.accept() { match info.sock.accept() {
Ok(Some((io, addr))) => Conn { Ok(Some((io, addr))) => Conn {
io, io,
token: info.token, token: info.token,
@@ -399,24 +458,19 @@ impl Accept {
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return, Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => return,
Err(ref e) if connection_error(e) => continue, Err(ref e) if connection_error(e) => continue,
Err(e) => { Err(e) => {
// deregister listener temporary
error!("Error accepting connection: {}", e); error!("Error accepting connection: {}", e);
if let Err(err) = self.deregister(info) { if let Err(err) = self.poll.deregister(&info.sock) {
error!("Can not deregister server socket {}", err); error!("Can not deregister server socket {}", err);
} }
// sleep after error. write the timeout to socket info as later // sleep after error
// the poll would need it mark which socket and when it's
// listener should be registered
info.timeout = Some(Instant::now() + Duration::from_millis(500)); info.timeout = Some(Instant::now() + Duration::from_millis(500));
// after the sleep a Timer interest is sent to Accept Poll let r = self.timer.1.clone();
let waker = self.waker.clone(); System::current().arbiter().send(Box::pin(async move {
System::current().arbiter().spawn(async move { delay_until(Instant::now() + Duration::from_millis(510)).await;
sleep(Duration::from_millis(510)).await; let _ = r.set_readiness(mio::Ready::readable());
waker.wake(WakerInterest::Timer); }));
});
return; return;
} }
} }
@@ -424,7 +478,7 @@ impl Accept {
return; return;
}; };
self.accept_one(sockets, msg); self.accept_one(msg);
} }
} }
} }

View File

@@ -1,42 +1,43 @@
use std::{ use std::pin::Pin;
future::Future, use std::task::{Context, Poll};
io, mem, use std::time::Duration;
pin::Pin, use std::{io, mem, net};
task::{Context, Poll},
time::Duration,
};
use actix_rt::{self as rt, net::TcpStream, time::sleep, System}; use actix_rt::net::TcpStream;
use actix_rt::time::{delay_until, Instant};
use actix_rt::{spawn, System};
use futures_channel::mpsc::{unbounded, UnboundedReceiver};
use futures_channel::oneshot;
use futures_util::future::ready;
use futures_util::stream::FuturesUnordered;
use futures_util::{future::Future, ready, stream::Stream, FutureExt, StreamExt};
use log::{error, info}; use log::{error, info};
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; use socket2::{Domain, Protocol, Socket, Type};
use tokio::sync::oneshot;
use crate::accept::AcceptLoop; use crate::accept::{AcceptLoop, AcceptNotify, Command};
use crate::config::{ConfiguredService, ServiceConfig}; use crate::config::{ConfiguredService, ServiceConfig};
use crate::server::{Server, ServerCommand}; use crate::server::{Server, ServerCommand};
use crate::service::{InternalServiceFactory, ServiceFactory, StreamNewService}; use crate::service::{InternalServiceFactory, ServiceFactory, StreamNewService};
use crate::signals::{Signal, Signals}; use crate::signals::{Signal, Signals};
use crate::socket::{MioListener, StdSocketAddr, StdTcpListener, ToSocketAddrs}; use crate::socket::StdListener;
use crate::socket::{MioTcpListener, MioTcpSocket}; use crate::worker::{self, Worker, WorkerAvailability, WorkerClient};
use crate::waker_queue::{WakerInterest, WakerQueue}; use crate::Token;
use crate::worker::{self, ServerWorker, ServerWorkerConfig, WorkerAvailability, WorkerHandle};
use crate::{join_all, Token};
/// Server builder /// Server builder
pub struct ServerBuilder { pub struct ServerBuilder {
threads: usize, threads: usize,
token: Token, token: Token,
backlog: u32, backlog: i32,
handles: Vec<(usize, WorkerHandle)>, workers: Vec<(usize, WorkerClient)>,
services: Vec<Box<dyn InternalServiceFactory>>, services: Vec<Box<dyn InternalServiceFactory>>,
sockets: Vec<(Token, String, MioListener)>, sockets: Vec<(Token, String, StdListener)>,
accept: AcceptLoop, accept: AcceptLoop,
exit: bool, exit: bool,
shutdown_timeout: Duration,
no_signals: bool, no_signals: bool,
cmd: UnboundedReceiver<ServerCommand>, cmd: UnboundedReceiver<ServerCommand>,
server: Server, server: Server,
notify: Vec<oneshot::Sender<()>>, notify: Vec<oneshot::Sender<()>>,
worker_config: ServerWorkerConfig,
} }
impl Default for ServerBuilder { impl Default for ServerBuilder {
@@ -48,23 +49,23 @@ impl Default for ServerBuilder {
impl ServerBuilder { impl ServerBuilder {
/// Create new Server builder instance /// Create new Server builder instance
pub fn new() -> ServerBuilder { pub fn new() -> ServerBuilder {
let (tx, rx) = unbounded_channel(); let (tx, rx) = unbounded();
let server = Server::new(tx); let server = Server::new(tx);
ServerBuilder { ServerBuilder {
threads: num_cpus::get(), threads: num_cpus::get(),
token: Token::default(), token: Token(0),
handles: Vec::new(), workers: Vec::new(),
services: Vec::new(), services: Vec::new(),
sockets: Vec::new(), sockets: Vec::new(),
accept: AcceptLoop::new(server.clone()), accept: AcceptLoop::new(server.clone()),
backlog: 2048, backlog: 2048,
exit: false, exit: false,
shutdown_timeout: Duration::from_secs(30),
no_signals: false, no_signals: false,
cmd: rx, cmd: rx,
notify: Vec::new(), notify: Vec::new(),
server, server,
worker_config: ServerWorkerConfig::default(),
} }
} }
@@ -78,24 +79,6 @@ impl ServerBuilder {
self self
} }
/// Set max number of threads for each worker's blocking task thread pool.
///
/// One thread pool is set up **per worker**; not shared across workers.
///
/// # Examples:
/// ```
/// # use actix_server::ServerBuilder;
/// let builder = ServerBuilder::new()
/// .workers(4) // server has 4 worker thread.
/// .worker_max_blocking_threads(4); // every worker has 4 max blocking threads.
/// ```
///
/// See [tokio::runtime::Builder::max_blocking_threads] for behavior reference.
pub fn worker_max_blocking_threads(mut self, num: usize) -> Self {
self.worker_config.max_blocking_threads(num);
self
}
/// Set the maximum number of pending connections. /// Set the maximum number of pending connections.
/// ///
/// This refers to the number of clients that can be waiting to be served. /// This refers to the number of clients that can be waiting to be served.
@@ -106,7 +89,7 @@ impl ServerBuilder {
/// Generally set in the 64-2048 range. Default value is 2048. /// Generally set in the 64-2048 range. Default value is 2048.
/// ///
/// This method should be called before `bind()` method call. /// This method should be called before `bind()` method call.
pub fn backlog(mut self, num: u32) -> Self { pub fn backlog(mut self, num: i32) -> Self {
self.backlog = num; self.backlog = num;
self self
} }
@@ -122,13 +105,13 @@ impl ServerBuilder {
self self
} }
/// Stop Actix system. /// Stop actix system.
pub fn system_exit(mut self) -> Self { pub fn system_exit(mut self) -> Self {
self.exit = true; self.exit = true;
self self
} }
/// Disable signal handling. /// Disable signal handling
pub fn disable_signals(mut self) -> Self { pub fn disable_signals(mut self) -> Self {
self.no_signals = true; self.no_signals = true;
self self
@@ -136,20 +119,21 @@ impl ServerBuilder {
/// Timeout for graceful workers shutdown in seconds. /// Timeout for graceful workers shutdown in seconds.
/// ///
/// After receiving a stop signal, workers have this much time to finish serving requests. /// After receiving a stop signal, workers have this much time to finish
/// Workers still alive after the timeout are force dropped. /// serving requests. Workers still alive after the timeout are force
/// dropped.
/// ///
/// By default shutdown timeout sets to 30 seconds. /// By default shutdown timeout sets to 30 seconds.
pub fn shutdown_timeout(mut self, sec: u64) -> Self { pub fn shutdown_timeout(mut self, sec: u64) -> Self {
self.worker_config self.shutdown_timeout = Duration::from_secs(sec);
.shutdown_timeout(Duration::from_secs(sec));
self self
} }
/// Execute external configuration as part of the server building process. /// Execute external configuration as part of the server building
/// process.
/// ///
/// This function is useful for moving parts of configuration to a different module or /// This function is useful for moving parts of configuration to a
/// even library. /// different module or even library.
pub fn configure<F>(mut self, f: F) -> io::Result<ServerBuilder> pub fn configure<F>(mut self, f: F) -> io::Result<ServerBuilder>
where where
F: Fn(&mut ServiceConfig) -> io::Result<()>, F: Fn(&mut ServiceConfig) -> io::Result<()>,
@@ -163,7 +147,7 @@ impl ServerBuilder {
for (name, lst) in cfg.services { for (name, lst) in cfg.services {
let token = self.token.next(); let token = self.token.next();
srv.stream(token, name.clone(), lst.local_addr()?); srv.stream(token, name.clone(), lst.local_addr()?);
self.sockets.push((token, name, MioListener::Tcp(lst))); self.sockets.push((token, name, StdListener::Tcp(lst)));
} }
self.services.push(Box::new(srv)); self.services.push(Box::new(srv));
} }
@@ -176,7 +160,7 @@ impl ServerBuilder {
pub fn bind<F, U, N: AsRef<str>>(mut self, name: N, addr: U, factory: F) -> io::Result<Self> pub fn bind<F, U, N: AsRef<str>>(mut self, name: N, addr: U, factory: F) -> io::Result<Self>
where where
F: ServiceFactory<TcpStream>, F: ServiceFactory<TcpStream>,
U: ToSocketAddrs, U: net::ToSocketAddrs,
{ {
let sockets = bind_addr(addr, self.backlog)?; let sockets = bind_addr(addr, self.backlog)?;
@@ -189,19 +173,21 @@ impl ServerBuilder {
lst.local_addr()?, lst.local_addr()?,
)); ));
self.sockets self.sockets
.push((token, name.as_ref().to_string(), MioListener::Tcp(lst))); .push((token, name.as_ref().to_string(), StdListener::Tcp(lst)));
} }
Ok(self) Ok(self)
} }
#[cfg(all(unix))]
/// Add new unix domain service to the server. /// Add new unix domain service to the server.
#[cfg(unix)]
pub fn bind_uds<F, U, N>(self, name: N, addr: U, factory: F) -> io::Result<Self> pub fn bind_uds<F, U, N>(self, name: N, addr: U, factory: F) -> io::Result<Self>
where where
F: ServiceFactory<actix_rt::net::UnixStream>, F: ServiceFactory<actix_rt::net::UnixStream>,
N: AsRef<str>, N: AsRef<str>,
U: AsRef<std::path::Path>, U: AsRef<std::path::Path>,
{ {
use std::os::unix::net::UnixListener;
// The path must not exist when we try to bind. // The path must not exist when we try to bind.
// Try to remove it to avoid bind error. // Try to remove it to avoid bind error.
if let Err(e) = std::fs::remove_file(addr.as_ref()) { if let Err(e) = std::fs::remove_file(addr.as_ref()) {
@@ -211,27 +197,26 @@ impl ServerBuilder {
} }
} }
let lst = crate::socket::StdUnixListener::bind(addr)?; let lst = UnixListener::bind(addr)?;
self.listen_uds(name, lst, factory) self.listen_uds(name, lst, factory)
} }
#[cfg(all(unix))]
/// Add new unix domain service to the server. /// Add new unix domain service to the server.
/// Useful when running as a systemd service and /// Useful when running as a systemd service and
/// a socket FD can be acquired using the systemd crate. /// a socket FD can be acquired using the systemd crate.
#[cfg(unix)]
pub fn listen_uds<F, N: AsRef<str>>( pub fn listen_uds<F, N: AsRef<str>>(
mut self, mut self,
name: N, name: N,
lst: crate::socket::StdUnixListener, lst: std::os::unix::net::UnixListener,
factory: F, factory: F,
) -> io::Result<Self> ) -> io::Result<Self>
where where
F: ServiceFactory<actix_rt::net::UnixStream>, F: ServiceFactory<actix_rt::net::UnixStream>,
{ {
use std::net::{IpAddr, Ipv4Addr}; use std::net::{IpAddr, Ipv4Addr, SocketAddr};
lst.set_nonblocking(true)?;
let token = self.token.next(); let token = self.token.next();
let addr = StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
self.services.push(StreamNewService::create( self.services.push(StreamNewService::create(
name.as_ref().to_string(), name.as_ref().to_string(),
token, token,
@@ -239,7 +224,7 @@ impl ServerBuilder {
addr, addr,
)); ));
self.sockets self.sockets
.push((token, name.as_ref().to_string(), MioListener::from(lst))); .push((token, name.as_ref().to_string(), StdListener::Uds(lst)));
Ok(self) Ok(self)
} }
@@ -247,29 +232,29 @@ impl ServerBuilder {
pub fn listen<F, N: AsRef<str>>( pub fn listen<F, N: AsRef<str>>(
mut self, mut self,
name: N, name: N,
lst: StdTcpListener, lst: net::TcpListener,
factory: F, factory: F,
) -> io::Result<Self> ) -> io::Result<Self>
where where
F: ServiceFactory<TcpStream>, F: ServiceFactory<TcpStream>,
{ {
lst.set_nonblocking(true)?;
let addr = lst.local_addr()?;
let token = self.token.next(); let token = self.token.next();
self.services.push(StreamNewService::create( self.services.push(StreamNewService::create(
name.as_ref().to_string(), name.as_ref().to_string(),
token, token,
factory, factory,
addr, lst.local_addr()?,
)); ));
self.sockets self.sockets
.push((token, name.as_ref().to_string(), MioListener::from(lst))); .push((token, name.as_ref().to_string(), StdListener::Tcp(lst)));
Ok(self) Ok(self)
} }
#[doc(hidden)]
pub fn start(self) -> Server {
self.run()
}
/// Starts processing incoming connections and return server controller. /// Starts processing incoming connections and return server controller.
pub fn run(mut self) -> Server { pub fn run(mut self) -> Server {
if self.sockets.is_empty() { if self.sockets.is_empty() {
@@ -278,12 +263,12 @@ impl ServerBuilder {
info!("Starting {} workers", self.threads); info!("Starting {} workers", self.threads);
// start workers // start workers
let handles = (0..self.threads) let workers = (0..self.threads)
.map(|idx| { .map(|idx| {
let handle = self.start_worker(idx, self.accept.waker_owned()); let worker = self.start_worker(idx, self.accept.get_notify());
self.handles.push((idx, handle.clone())); self.workers.push((idx, worker.clone()));
handle worker
}) })
.collect(); .collect();
@@ -296,7 +281,7 @@ impl ServerBuilder {
.into_iter() .into_iter()
.map(|t| (t.0, t.2)) .map(|t| (t.0, t.2))
.collect(), .collect(),
handles, workers,
); );
// handle signals // handle signals
@@ -306,26 +291,27 @@ impl ServerBuilder {
// start http server actor // start http server actor
let server = self.server.clone(); let server = self.server.clone();
rt::spawn(self); spawn(self);
server server
} }
} }
fn start_worker(&self, idx: usize, waker: WakerQueue) -> WorkerHandle { fn start_worker(&self, idx: usize, notify: AcceptNotify) -> WorkerClient {
let avail = WorkerAvailability::new(waker); let avail = WorkerAvailability::new(notify);
let services = self.services.iter().map(|v| v.clone_factory()).collect(); let services: Vec<Box<dyn InternalServiceFactory>> =
self.services.iter().map(|v| v.clone_factory()).collect();
ServerWorker::start(idx, services, avail, self.worker_config) Worker::start(idx, services, avail, self.shutdown_timeout)
} }
fn handle_cmd(&mut self, item: ServerCommand) { fn handle_cmd(&mut self, item: ServerCommand) {
match item { match item {
ServerCommand::Pause(tx) => { ServerCommand::Pause(tx) => {
self.accept.wake(WakerInterest::Pause); self.accept.send(Command::Pause);
let _ = tx.send(()); let _ = tx.send(());
} }
ServerCommand::Resume(tx) => { ServerCommand::Resume(tx) => {
self.accept.wake(WakerInterest::Resume); self.accept.send(Command::Resume);
let _ = tx.send(()); let _ = tx.send(());
} }
ServerCommand::Signal(sig) => { ServerCommand::Signal(sig) => {
@@ -369,41 +355,50 @@ impl ServerBuilder {
let exit = self.exit; let exit = self.exit;
// stop accept thread // stop accept thread
self.accept.wake(WakerInterest::Stop); self.accept.send(Command::Stop);
let notify = std::mem::take(&mut self.notify); let notify = std::mem::take(&mut self.notify);
// stop workers // stop workers
if !self.handles.is_empty() && graceful { if !self.workers.is_empty() && graceful {
let iter = self spawn(
.handles self.workers
.iter() .iter()
.map(move |worker| worker.1.stop(graceful)) .map(move |worker| worker.1.stop(graceful))
.collect(); .collect::<FuturesUnordered<_>>()
.collect::<Vec<_>>()
let fut = join_all(iter); .then(move |_| {
if let Some(tx) = completion {
rt::spawn(async move { let _ = tx.send(());
let _ = fut.await; }
if let Some(tx) = completion { for tx in notify {
let _ = tx.send(()); let _ = tx.send(());
} }
for tx in notify { if exit {
let _ = tx.send(()); spawn(
} async {
if exit { delay_until(
rt::spawn(async { Instant::now() + Duration::from_millis(300),
sleep(Duration::from_millis(300)).await; )
System::current().stop(); .await;
}); System::current().stop();
} }
}); .boxed(),
);
}
ready(())
}),
)
} else { } else {
// we need to stop system if server was spawned // we need to stop system if server was spawned
if self.exit { if self.exit {
rt::spawn(async { spawn(
sleep(Duration::from_millis(300)).await; delay_until(Instant::now() + Duration::from_millis(300)).then(
System::current().stop(); |_| {
}); System::current().stop();
ready(())
},
),
);
} }
if let Some(tx) = completion { if let Some(tx) = completion {
let _ = tx.send(()); let _ = tx.send(());
@@ -415,9 +410,9 @@ impl ServerBuilder {
} }
ServerCommand::WorkerFaulted(idx) => { ServerCommand::WorkerFaulted(idx) => {
let mut found = false; let mut found = false;
for i in 0..self.handles.len() { for i in 0..self.workers.len() {
if self.handles[i].0 == idx { if self.workers[i].0 == idx {
self.handles.swap_remove(i); self.workers.swap_remove(i);
found = true; found = true;
break; break;
} }
@@ -426,10 +421,10 @@ impl ServerBuilder {
if found { if found {
error!("Worker has died {:?}, restarting", idx); error!("Worker has died {:?}, restarting", idx);
let mut new_idx = self.handles.len(); let mut new_idx = self.workers.len();
'found: loop { 'found: loop {
for i in 0..self.handles.len() { for i in 0..self.workers.len() {
if self.handles[i].0 == new_idx { if self.workers[i].0 == new_idx {
new_idx += 1; new_idx += 1;
continue 'found; continue 'found;
} }
@@ -437,9 +432,9 @@ impl ServerBuilder {
break; break;
} }
let handle = self.start_worker(new_idx, self.accept.waker_owned()); let worker = self.start_worker(new_idx, self.accept.get_notify());
self.handles.push((new_idx, handle.clone())); self.workers.push((new_idx, worker.clone()));
self.accept.wake(WakerInterest::Worker(handle)); self.accept.send(Command::Worker(worker));
} }
} }
} }
@@ -451,18 +446,20 @@ impl Future for ServerBuilder {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
loop { loop {
match Pin::new(&mut self.cmd).poll_recv(cx) { match ready!(Pin::new(&mut self.cmd).poll_next(cx)) {
Poll::Ready(Some(it)) => self.as_mut().get_mut().handle_cmd(it), Some(it) => self.as_mut().get_mut().handle_cmd(it),
_ => return Poll::Pending, None => {
return Poll::Pending;
}
} }
} }
} }
} }
pub(super) fn bind_addr<S: ToSocketAddrs>( pub(super) fn bind_addr<S: net::ToSocketAddrs>(
addr: S, addr: S,
backlog: u32, backlog: i32,
) -> io::Result<Vec<MioTcpListener>> { ) -> io::Result<Vec<net::TcpListener>> {
let mut err = None; let mut err = None;
let mut succ = false; let mut succ = false;
let mut sockets = Vec::new(); let mut sockets = Vec::new();
@@ -490,13 +487,14 @@ pub(super) fn bind_addr<S: ToSocketAddrs>(
} }
} }
fn create_tcp_listener(addr: StdSocketAddr, backlog: u32) -> io::Result<MioTcpListener> { fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result<net::TcpListener> {
let socket = match addr { let domain = match addr {
StdSocketAddr::V4(_) => MioTcpSocket::new_v4()?, net::SocketAddr::V4(_) => Domain::ipv4(),
StdSocketAddr::V6(_) => MioTcpSocket::new_v6()?, net::SocketAddr::V6(_) => Domain::ipv6(),
}; };
let socket = Socket::new(domain, Type::stream(), Some(Protocol::tcp()))?;
socket.set_reuseaddr(true)?; socket.set_reuse_address(true)?;
socket.bind(addr)?; socket.bind(&addr.into())?;
socket.listen(backlog) socket.listen(backlog)?;
Ok(socket.into_tcp_listener())
} }

View File

@@ -1,30 +1,26 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::future::Future; use std::{fmt, io, net};
use std::{fmt, io};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
use actix_service::{ use actix_service as actix;
fn_service, IntoServiceFactory as IntoBaseServiceFactory,
ServiceFactory as BaseServiceFactory,
};
use actix_utils::counter::CounterGuard; use actix_utils::counter::CounterGuard;
use futures_core::future::LocalBoxFuture; use futures_util::future::{ok, Future, FutureExt, LocalBoxFuture};
use log::error; use log::error;
use crate::builder::bind_addr; use super::builder::bind_addr;
use crate::service::{BoxedServerService, InternalServiceFactory, StreamService}; use super::service::{BoxedServerService, InternalServiceFactory, StreamService};
use crate::socket::{MioStream, MioTcpListener, StdSocketAddr, StdTcpListener, ToSocketAddrs}; use super::Token;
use crate::{ready, Token}; use crate::socket::StdStream;
pub struct ServiceConfig { pub struct ServiceConfig {
pub(crate) services: Vec<(String, MioTcpListener)>, pub(crate) services: Vec<(String, net::TcpListener)>,
pub(crate) apply: Option<Box<dyn ServiceRuntimeConfiguration>>, pub(crate) apply: Option<Box<dyn ServiceRuntimeConfiguration>>,
pub(crate) threads: usize, pub(crate) threads: usize,
pub(crate) backlog: u32, pub(crate) backlog: i32,
} }
impl ServiceConfig { impl ServiceConfig {
pub(super) fn new(threads: usize, backlog: u32) -> ServiceConfig { pub(super) fn new(threads: usize, backlog: i32) -> ServiceConfig {
ServiceConfig { ServiceConfig {
threads, threads,
backlog, backlog,
@@ -44,20 +40,24 @@ impl ServiceConfig {
/// Add new service to server /// Add new service to server
pub fn bind<U, N: AsRef<str>>(&mut self, name: N, addr: U) -> io::Result<&mut Self> pub fn bind<U, N: AsRef<str>>(&mut self, name: N, addr: U) -> io::Result<&mut Self>
where where
U: ToSocketAddrs, U: net::ToSocketAddrs,
{ {
let sockets = bind_addr(addr, self.backlog)?; let sockets = bind_addr(addr, self.backlog)?;
for lst in sockets { for lst in sockets {
self._listen(name.as_ref(), lst); self.listen(name.as_ref(), lst);
} }
Ok(self) Ok(self)
} }
/// Add new service to server /// Add new service to server
pub fn listen<N: AsRef<str>>(&mut self, name: N, lst: StdTcpListener) -> &mut Self { pub fn listen<N: AsRef<str>>(&mut self, name: N, lst: net::TcpListener) -> &mut Self {
self._listen(name, MioTcpListener::from_std(lst)) if self.apply.is_none() {
self.apply = Some(Box::new(not_configured));
}
self.services.push((name.as_ref().to_string(), lst));
self
} }
/// Register service configuration function. This function get called /// Register service configuration function. This function get called
@@ -69,19 +69,11 @@ impl ServiceConfig {
self.apply = Some(Box::new(f)); self.apply = Some(Box::new(f));
Ok(()) Ok(())
} }
fn _listen<N: AsRef<str>>(&mut self, name: N, lst: MioTcpListener) -> &mut Self {
if self.apply.is_none() {
self.apply = Some(Box::new(not_configured));
}
self.services.push((name.as_ref().to_string(), lst));
self
}
} }
pub(super) struct ConfiguredService { pub(super) struct ConfiguredService {
rt: Box<dyn ServiceRuntimeConfiguration>, rt: Box<dyn ServiceRuntimeConfiguration>,
names: HashMap<Token, (String, StdSocketAddr)>, names: HashMap<Token, (String, net::SocketAddr)>,
topics: HashMap<String, Token>, topics: HashMap<String, Token>,
services: Vec<Token>, services: Vec<Token>,
} }
@@ -96,7 +88,7 @@ impl ConfiguredService {
} }
} }
pub(super) fn stream(&mut self, token: Token, name: String, addr: StdSocketAddr) { pub(super) fn stream(&mut self, token: Token, name: String, addr: net::SocketAddr) {
self.names.insert(token, (name.clone(), addr)); self.names.insert(token, (name.clone(), addr));
self.topics.insert(name, token); self.topics.insert(name, token);
self.services.push(token); self.services.push(token);
@@ -126,7 +118,7 @@ impl InternalServiceFactory for ConfiguredService {
let tokens = self.services.clone(); let tokens = self.services.clone();
// construct services // construct services
Box::pin(async move { async move {
let mut services = rt.services; let mut services = rt.services;
// TODO: Proper error handling here // TODO: Proper error handling here
for f in rt.onstart.into_iter() { for f in rt.onstart.into_iter() {
@@ -149,15 +141,18 @@ impl InternalServiceFactory for ConfiguredService {
let name = names.remove(&token).unwrap().0; let name = names.remove(&token).unwrap().0;
res.push(( res.push((
token, token,
Box::new(StreamService::new(fn_service(move |_: TcpStream| { Box::new(StreamService::new(actix::fn_service(
error!("Service {:?} is not configured", name); move |_: TcpStream| {
ready::<Result<_, ()>>(Ok(())) error!("Service {:?} is not configured", name);
}))), ok::<_, ()>(())
},
))),
)); ));
}; };
} }
Ok(res) Ok(res)
}) }
.boxed_local()
} }
} }
@@ -213,8 +208,8 @@ impl ServiceRuntime {
/// *ServiceConfig::bind()* or *ServiceConfig::listen()* methods. /// *ServiceConfig::bind()* or *ServiceConfig::listen()* methods.
pub fn service<T, F>(&mut self, name: &str, service: F) pub fn service<T, F>(&mut self, name: &str, service: F)
where where
F: IntoBaseServiceFactory<T, TcpStream>, F: actix::IntoServiceFactory<T>,
T: BaseServiceFactory<TcpStream, Config = ()> + 'static, T: actix::ServiceFactory<Config = (), Request = TcpStream> + 'static,
T::Future: 'static, T::Future: 'static,
T::Service: 'static, T::Service: 'static,
T::InitError: fmt::Debug, T::InitError: fmt::Debug,
@@ -237,13 +232,13 @@ impl ServiceRuntime {
where where
F: Future<Output = ()> + 'static, F: Future<Output = ()> + 'static,
{ {
self.onstart.push(Box::pin(fut)) self.onstart.push(fut.boxed_local())
} }
} }
type BoxedNewService = Box< type BoxedNewService = Box<
dyn BaseServiceFactory< dyn actix::ServiceFactory<
(Option<CounterGuard>, MioStream), Request = (Option<CounterGuard>, StdStream),
Response = (), Response = (),
Error = (), Error = (),
InitError = (), InitError = (),
@@ -257,14 +252,15 @@ struct ServiceFactory<T> {
inner: T, inner: T,
} }
impl<T> BaseServiceFactory<(Option<CounterGuard>, MioStream)> for ServiceFactory<T> impl<T> actix::ServiceFactory for ServiceFactory<T>
where where
T: BaseServiceFactory<TcpStream, Config = ()>, T: actix::ServiceFactory<Config = (), Request = TcpStream>,
T::Future: 'static, T::Future: 'static,
T::Service: 'static, T::Service: 'static,
T::Error: 'static, T::Error: 'static,
T::InitError: fmt::Debug + 'static, T::InitError: fmt::Debug + 'static,
{ {
type Request = (Option<CounterGuard>, StdStream);
type Response = (); type Response = ();
type Error = (); type Error = ();
type Config = (); type Config = ();
@@ -274,7 +270,7 @@ where
fn new_service(&self, _: ()) -> Self::Future { fn new_service(&self, _: ()) -> Self::Future {
let fut = self.inner.new_service(()); let fut = self.inner.new_service(());
Box::pin(async move { async move {
match fut.await { match fut.await {
Ok(s) => Ok(Box::new(StreamService::new(s)) as BoxedServerService), Ok(s) => Ok(Box::new(StreamService::new(s)) as BoxedServerService),
Err(e) => { Err(e) => {
@@ -282,6 +278,7 @@ where
Err(()) Err(())
} }
} }
}) }
.boxed_local()
} }
} }

View File

@@ -11,38 +11,21 @@ mod server;
mod service; mod service;
mod signals; mod signals;
mod socket; mod socket;
mod test_server;
mod waker_queue;
mod worker; mod worker;
pub use self::builder::ServerBuilder; pub use self::builder::ServerBuilder;
pub use self::config::{ServiceConfig, ServiceRuntime}; pub use self::config::{ServiceConfig, ServiceRuntime};
pub use self::server::Server; pub use self::server::Server;
pub use self::service::ServiceFactory; pub use self::service::ServiceFactory;
pub use self::test_server::TestServer;
#[doc(hidden)] #[doc(hidden)]
pub use self::socket::FromStream; pub use self::socket::FromStream;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
/// Socket ID token /// Socket ID token
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct Token(usize); pub(crate) struct Token(usize);
impl Default for Token {
fn default() -> Self {
Self::new()
}
}
impl Token { impl Token {
fn new() -> Self {
Self(0)
}
pub(crate) fn next(&mut self) -> Token { pub(crate) fn next(&mut self) -> Token {
let token = Token(self.0); let token = Token(self.0);
self.0 += 1; self.0 += 1;
@@ -54,90 +37,3 @@ impl Token {
pub fn new() -> ServerBuilder { pub fn new() -> ServerBuilder {
ServerBuilder::default() ServerBuilder::default()
} }
// temporary Ready type for std::future::{ready, Ready}; Can be removed when MSRV surpass 1.48
#[doc(hidden)]
pub struct Ready<T>(Option<T>);
pub(crate) fn ready<T>(t: T) -> Ready<T> {
Ready(Some(t))
}
impl<T> Unpin for Ready<T> {}
impl<T> Future for Ready<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(self.get_mut().0.take().unwrap())
}
}
// a poor man's join future. joined future is only used when starting/stopping the server.
// pin_project and pinned futures are overkill for this task.
pub(crate) struct JoinAll<T> {
fut: Vec<JoinFuture<T>>,
}
pub(crate) fn join_all<T>(fut: Vec<impl Future<Output = T> + 'static>) -> JoinAll<T> {
let fut = fut
.into_iter()
.map(|f| JoinFuture::Future(Box::pin(f)))
.collect();
JoinAll { fut }
}
enum JoinFuture<T> {
Future(Pin<Box<dyn Future<Output = T>>>),
Result(Option<T>),
}
impl<T> Unpin for JoinAll<T> {}
impl<T> Future for JoinAll<T> {
type Output = Vec<T>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut ready = true;
let this = self.get_mut();
for fut in this.fut.iter_mut() {
if let JoinFuture::Future(f) = fut {
match f.as_mut().poll(cx) {
Poll::Ready(t) => {
*fut = JoinFuture::Result(Some(t));
}
Poll::Pending => ready = false,
}
}
}
if ready {
let mut res = Vec::new();
for fut in this.fut.iter_mut() {
if let JoinFuture::Result(f) = fut {
res.push(f.take().unwrap());
}
}
Poll::Ready(res)
} else {
Poll::Pending
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[actix_rt::test]
async fn test_join_all() {
let futs = vec![ready(Ok(1)), ready(Err(3)), ready(Ok(9))];
let mut res = join_all(futs).await.into_iter();
assert_eq!(Ok(1), res.next().unwrap());
assert_eq!(Err(3), res.next().unwrap());
assert_eq!(Ok(9), res.next().unwrap());
}
}

View File

@@ -3,8 +3,9 @@ use std::io;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use tokio::sync::mpsc::UnboundedSender; use futures_channel::mpsc::UnboundedSender;
use tokio::sync::oneshot; use futures_channel::oneshot;
use futures_util::FutureExt;
use crate::builder::ServerBuilder; use crate::builder::ServerBuilder;
use crate::signals::Signal; use crate::signals::Signal;
@@ -41,11 +42,11 @@ impl Server {
} }
pub(crate) fn signal(&self, sig: Signal) { pub(crate) fn signal(&self, sig: Signal) {
let _ = self.0.send(ServerCommand::Signal(sig)); let _ = self.0.unbounded_send(ServerCommand::Signal(sig));
} }
pub(crate) fn worker_faulted(&self, idx: usize) { pub(crate) fn worker_faulted(&self, idx: usize) {
let _ = self.0.send(ServerCommand::WorkerFaulted(idx)); let _ = self.0.unbounded_send(ServerCommand::WorkerFaulted(idx));
} }
/// Pause accepting incoming connections /// Pause accepting incoming connections
@@ -54,19 +55,15 @@ impl Server {
/// All opened connection remains active. /// All opened connection remains active.
pub fn pause(&self) -> impl Future<Output = ()> { pub fn pause(&self) -> impl Future<Output = ()> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let _ = self.0.send(ServerCommand::Pause(tx)); let _ = self.0.unbounded_send(ServerCommand::Pause(tx));
async { rx.map(|_| ())
let _ = rx.await;
}
} }
/// Resume accepting incoming connections /// Resume accepting incoming connections
pub fn resume(&self) -> impl Future<Output = ()> { pub fn resume(&self) -> impl Future<Output = ()> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let _ = self.0.send(ServerCommand::Resume(tx)); let _ = self.0.unbounded_send(ServerCommand::Resume(tx));
async { rx.map(|_| ())
let _ = rx.await;
}
} }
/// Stop incoming connection processing, stop all workers and exit. /// Stop incoming connection processing, stop all workers and exit.
@@ -74,13 +71,11 @@ impl Server {
/// If server starts with `spawn()` method, then spawned thread get terminated. /// If server starts with `spawn()` method, then spawned thread get terminated.
pub fn stop(&self, graceful: bool) -> impl Future<Output = ()> { pub fn stop(&self, graceful: bool) -> impl Future<Output = ()> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let _ = self.0.send(ServerCommand::Stop { let _ = self.0.unbounded_send(ServerCommand::Stop {
graceful, graceful,
completion: Some(tx), completion: Some(tx),
}); });
async { rx.map(|_| ())
let _ = rx.await;
}
} }
} }
@@ -98,7 +93,7 @@ impl Future for Server {
if this.1.is_none() { if this.1.is_none() {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
if this.0.send(ServerCommand::Notify(tx)).is_err() { if this.0.unbounded_send(ServerCommand::Notify(tx)).is_err() {
return Poll::Ready(Ok(())); return Poll::Ready(Ok(()));
} }
this.1 = Some(rx); this.1 = Some(rx);
@@ -106,7 +101,8 @@ impl Future for Server {
match Pin::new(this.1.as_mut().unwrap()).poll(cx) { match Pin::new(this.1.as_mut().unwrap()).poll(cx) {
Poll::Pending => Poll::Pending, Poll::Pending => Poll::Pending,
Poll::Ready(_) => Poll::Ready(Ok(())), Poll::Ready(Ok(_)) => Poll::Ready(Ok(())),
Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
} }
} }
} }

View File

@@ -2,16 +2,18 @@ use std::marker::PhantomData;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_service::{Service, ServiceFactory as BaseServiceFactory}; use actix_rt::spawn;
use actix_service::{self as actix, Service, ServiceFactory as ActixServiceFactory};
use actix_utils::counter::CounterGuard; use actix_utils::counter::CounterGuard;
use futures_core::future::LocalBoxFuture; use futures_util::future::{err, ok, LocalBoxFuture, Ready};
use futures_util::{FutureExt, TryFutureExt};
use log::error; use log::error;
use crate::socket::{FromStream, MioStream}; use super::Token;
use crate::{ready, Ready, Token}; use crate::socket::{FromStream, StdStream};
pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static { pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static {
type Factory: BaseServiceFactory<Stream, Config = ()>; type Factory: actix::ServiceFactory<Config = (), Request = Stream>;
fn create(&self) -> Self::Factory; fn create(&self) -> Self::Factory;
} }
@@ -26,57 +28,54 @@ pub(crate) trait InternalServiceFactory: Send {
pub(crate) type BoxedServerService = Box< pub(crate) type BoxedServerService = Box<
dyn Service< dyn Service<
(Option<CounterGuard>, MioStream), Request = (Option<CounterGuard>, StdStream),
Response = (), Response = (),
Error = (), Error = (),
Future = Ready<Result<(), ()>>, Future = Ready<Result<(), ()>>,
>, >,
>; >;
pub(crate) struct StreamService<S, I> { pub(crate) struct StreamService<T> {
service: S, service: T,
_phantom: PhantomData<I>,
} }
impl<S, I> StreamService<S, I> { impl<T> StreamService<T> {
pub(crate) fn new(service: S) -> Self { pub(crate) fn new(service: T) -> Self {
StreamService { StreamService { service }
service,
_phantom: PhantomData,
}
} }
} }
impl<S, I> Service<(Option<CounterGuard>, MioStream)> for StreamService<S, I> impl<T, I> Service for StreamService<T>
where where
S: Service<I>, T: Service<Request = I>,
S::Future: 'static, T::Future: 'static,
S::Error: 'static, T::Error: 'static,
I: FromStream, I: FromStream,
{ {
type Request = (Option<CounterGuard>, StdStream);
type Response = (); type Response = ();
type Error = (); type Error = ();
type Future = Ready<Result<(), ()>>; type Future = Ready<Result<(), ()>>;
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(ctx).map_err(|_| ()) self.service.poll_ready(ctx).map_err(|_| ())
} }
fn call(&self, (guard, req): (Option<CounterGuard>, MioStream)) -> Self::Future { fn call(&mut self, (guard, req): (Option<CounterGuard>, StdStream)) -> Self::Future {
ready(match FromStream::from_mio(req) { match FromStream::from_stdstream(req) {
Ok(stream) => { Ok(stream) => {
let f = self.service.call(stream); let f = self.service.call(stream);
actix_rt::spawn(async move { spawn(async move {
let _ = f.await; let _ = f.await;
drop(guard); drop(guard);
}); });
Ok(()) ok(())
} }
Err(e) => { Err(e) => {
error!("Can not convert to an async tcp stream: {}", e); error!("Can not convert to an async tcp stream: {}", e);
Err(()) err(())
} }
}) }
} }
} }
@@ -130,23 +129,22 @@ where
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> { fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> {
let token = self.token; let token = self.token;
let fut = self.inner.create().new_service(()); self.inner
Box::pin(async move { .create()
match fut.await { .new_service(())
Ok(inner) => { .map_err(|_| ())
let service = Box::new(StreamService::new(inner)) as _; .map_ok(move |inner| {
Ok(vec![(token, service)]) let service: BoxedServerService = Box::new(StreamService::new(inner));
} vec![(token, service)]
Err(_) => Err(()), })
} .boxed_local()
})
} }
} }
impl<F, T, I> ServiceFactory<I> for F impl<F, T, I> ServiceFactory<I> for F
where where
F: Fn() -> T + Send + Clone + 'static, F: Fn() -> T + Send + Clone + 'static,
T: BaseServiceFactory<I, Config = ()>, T: actix::ServiceFactory<Config = (), Request = I>,
I: FromStream, I: FromStream,
{ {
type Factory = T; type Factory = T;

View File

@@ -2,6 +2,8 @@ use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use futures_util::future::lazy;
use crate::server::Server; use crate::server::Server;
/// Different types of process signals /// Different types of process signals
@@ -21,49 +23,48 @@ pub(crate) enum Signal {
pub(crate) struct Signals { pub(crate) struct Signals {
srv: Server, srv: Server,
#[cfg(not(unix))] #[cfg(not(unix))]
signals: futures_core::future::LocalBoxFuture<'static, std::io::Result<()>>, stream: Pin<Box<dyn Future<Output = std::io::Result<()>>>>,
#[cfg(unix)] #[cfg(unix)]
signals: Vec<(Signal, actix_rt::signal::unix::Signal)>, streams: Vec<(Signal, actix_rt::signal::unix::Signal)>,
} }
impl Signals { impl Signals {
pub(crate) fn start(srv: Server) { pub(crate) fn start(srv: Server) {
#[cfg(not(unix))] actix_rt::spawn(lazy(|_| {
{ #[cfg(not(unix))]
actix_rt::spawn(Signals { {
srv, actix_rt::spawn(Signals {
signals: Box::pin(actix_rt::signal::ctrl_c()), srv,
}); stream: Box::pin(actix_rt::signal::ctrl_c()),
} });
#[cfg(unix)] }
{ #[cfg(unix)]
use actix_rt::signal::unix; {
use actix_rt::signal::unix;
let sig_map = [ let mut streams = Vec::new();
(unix::SignalKind::interrupt(), Signal::Int),
(unix::SignalKind::hangup(), Signal::Hup),
(unix::SignalKind::terminate(), Signal::Term),
(unix::SignalKind::quit(), Signal::Quit),
];
let signals = sig_map let sig_map = [
.iter() (unix::SignalKind::interrupt(), Signal::Int),
.filter_map(|(kind, sig)| { (unix::SignalKind::hangup(), Signal::Hup),
unix::signal(*kind) (unix::SignalKind::terminate(), Signal::Term),
.map(|tokio_sig| (*sig, tokio_sig)) (unix::SignalKind::quit(), Signal::Quit),
.map_err(|e| { ];
log::error!(
"Can not initialize stream handler for {:?} err: {}",
sig,
e
)
})
.ok()
})
.collect::<Vec<_>>();
actix_rt::spawn(Signals { srv, signals }); for (kind, sig) in sig_map.iter() {
} match unix::signal(*kind) {
Ok(stream) => streams.push((*sig, stream)),
Err(e) => log::error!(
"Can not initialize stream handler for {:?} err: {}",
sig,
e
),
}
}
actix_rt::spawn(Signals { srv, streams })
}
}));
} }
} }
@@ -72,20 +73,25 @@ impl Future for Signals {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
#[cfg(not(unix))] #[cfg(not(unix))]
match self.signals.as_mut().poll(cx) { match Pin::new(&mut self.stream).poll(cx) {
Poll::Ready(_) => { Poll::Ready(_) => {
self.srv.signal(Signal::Int); self.srv.signal(Signal::Int);
Poll::Ready(()) Poll::Ready(())
} }
Poll::Pending => Poll::Pending, Poll::Pending => return Poll::Pending,
} }
#[cfg(unix)] #[cfg(unix)]
{ {
for (sig, fut) in self.signals.iter_mut() { for idx in 0..self.streams.len() {
if Pin::new(fut).poll_recv(cx).is_ready() { loop {
let sig = *sig; match self.streams[idx].1.poll_recv(cx) {
self.srv.signal(sig); Poll::Ready(None) => return Poll::Ready(()),
return Poll::Ready(()); Poll::Pending => break,
Poll::Ready(Some(_)) => {
let sig = self.streams[idx].0;
self.srv.signal(sig);
}
}
} }
} }
Poll::Pending Poll::Pending

View File

@@ -1,91 +1,135 @@
pub(crate) use std::net::{ use std::{fmt, io, net};
SocketAddr as StdSocketAddr, TcpListener as StdTcpListener, ToSocketAddrs,
};
pub(crate) use mio::net::{TcpListener as MioTcpListener, TcpSocket as MioTcpSocket};
#[cfg(unix)]
pub(crate) use {
mio::net::UnixListener as MioUnixListener,
std::os::unix::net::UnixListener as StdUnixListener,
};
use std::{fmt, io};
use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
use mio::event::Source;
use mio::net::TcpStream as MioTcpStream;
use mio::{Interest, Registry, Token};
#[cfg(windows)] pub(crate) enum StdListener {
use std::os::windows::io::{FromRawSocket, IntoRawSocket}; Tcp(net::TcpListener),
#[cfg(unix)] #[cfg(all(unix))]
use { Uds(std::os::unix::net::UnixListener),
actix_rt::net::UnixStream,
mio::net::{SocketAddr as MioSocketAddr, UnixStream as MioUnixStream},
std::os::unix::io::{FromRawFd, IntoRawFd},
};
pub(crate) enum MioListener {
Tcp(MioTcpListener),
#[cfg(unix)]
Uds(MioUnixListener),
} }
impl MioListener { pub(crate) enum SocketAddr {
Tcp(net::SocketAddr),
#[cfg(all(unix))]
Uds(std::os::unix::net::SocketAddr),
}
impl fmt::Display for SocketAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
SocketAddr::Tcp(ref addr) => write!(f, "{}", addr),
#[cfg(all(unix))]
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
}
}
}
impl fmt::Debug for SocketAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
SocketAddr::Tcp(ref addr) => write!(f, "{:?}", addr),
#[cfg(all(unix))]
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
}
}
}
impl fmt::Display for StdListener {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
StdListener::Tcp(ref lst) => write!(f, "{}", lst.local_addr().ok().unwrap()),
#[cfg(all(unix))]
StdListener::Uds(ref lst) => write!(f, "{:?}", lst.local_addr().ok().unwrap()),
}
}
}
impl StdListener {
pub(crate) fn local_addr(&self) -> SocketAddr { pub(crate) fn local_addr(&self) -> SocketAddr {
match *self { match self {
MioListener::Tcp(ref lst) => SocketAddr::Tcp(lst.local_addr().unwrap()), StdListener::Tcp(lst) => SocketAddr::Tcp(lst.local_addr().unwrap()),
#[cfg(unix)] #[cfg(all(unix))]
MioListener::Uds(ref lst) => SocketAddr::Uds(lst.local_addr().unwrap()), StdListener::Uds(lst) => SocketAddr::Uds(lst.local_addr().unwrap()),
} }
} }
pub(crate) fn accept(&self) -> io::Result<Option<(MioStream, SocketAddr)>> { pub(crate) fn into_listener(self) -> SocketListener {
match *self { match self {
MioListener::Tcp(ref lst) => lst StdListener::Tcp(lst) => SocketListener::Tcp(
.accept() mio::net::TcpListener::from_std(lst)
.map(|(stream, addr)| Some((MioStream::Tcp(stream), SocketAddr::Tcp(addr)))), .expect("Can not create mio::net::TcpListener"),
#[cfg(unix)] ),
MioListener::Uds(ref lst) => lst #[cfg(all(unix))]
.accept() StdListener::Uds(lst) => SocketListener::Uds(
.map(|(stream, addr)| Some((MioStream::Uds(stream), SocketAddr::Uds(addr)))), mio_uds::UnixListener::from_listener(lst)
.expect("Can not create mio_uds::UnixListener"),
),
} }
} }
} }
impl Source for MioListener { #[derive(Debug)]
pub enum StdStream {
Tcp(std::net::TcpStream),
#[cfg(all(unix))]
Uds(std::os::unix::net::UnixStream),
}
pub(crate) enum SocketListener {
Tcp(mio::net::TcpListener),
#[cfg(all(unix))]
Uds(mio_uds::UnixListener),
}
impl SocketListener {
pub(crate) fn accept(&self) -> io::Result<Option<(StdStream, SocketAddr)>> {
match *self {
SocketListener::Tcp(ref lst) => lst
.accept_std()
.map(|(stream, addr)| Some((StdStream::Tcp(stream), SocketAddr::Tcp(addr)))),
#[cfg(all(unix))]
SocketListener::Uds(ref lst) => lst.accept_std().map(|res| {
res.map(|(stream, addr)| (StdStream::Uds(stream), SocketAddr::Uds(addr)))
}),
}
}
}
impl mio::Evented for SocketListener {
fn register( fn register(
&mut self, &self,
registry: &Registry, poll: &mio::Poll,
token: Token, token: mio::Token,
interests: Interest, interest: mio::Ready,
opts: mio::PollOpt,
) -> io::Result<()> { ) -> io::Result<()> {
match *self { match *self {
MioListener::Tcp(ref mut lst) => lst.register(registry, token, interests), SocketListener::Tcp(ref lst) => lst.register(poll, token, interest, opts),
#[cfg(unix)] #[cfg(all(unix))]
MioListener::Uds(ref mut lst) => lst.register(registry, token, interests), SocketListener::Uds(ref lst) => lst.register(poll, token, interest, opts),
} }
} }
fn reregister( fn reregister(
&mut self, &self,
registry: &Registry, poll: &mio::Poll,
token: Token, token: mio::Token,
interests: Interest, interest: mio::Ready,
opts: mio::PollOpt,
) -> io::Result<()> { ) -> io::Result<()> {
match *self { match *self {
MioListener::Tcp(ref mut lst) => lst.reregister(registry, token, interests), SocketListener::Tcp(ref lst) => lst.reregister(poll, token, interest, opts),
#[cfg(unix)] #[cfg(all(unix))]
MioListener::Uds(ref mut lst) => lst.reregister(registry, token, interests), SocketListener::Uds(ref lst) => lst.reregister(poll, token, interest, opts),
} }
} }
fn deregister(&self, poll: &mio::Poll) -> io::Result<()> {
fn deregister(&mut self, registry: &Registry) -> io::Result<()> {
match *self { match *self {
MioListener::Tcp(ref mut lst) => lst.deregister(registry), SocketListener::Tcp(ref lst) => lst.deregister(poll),
#[cfg(unix)] #[cfg(all(unix))]
MioListener::Uds(ref mut lst) => { SocketListener::Uds(ref lst) => {
let res = lst.deregister(registry); let res = lst.deregister(poll);
// cleanup file path // cleanup file path
if let Ok(addr) = lst.local_addr() { if let Ok(addr) = lst.local_addr() {
@@ -99,156 +143,28 @@ impl Source for MioListener {
} }
} }
impl From<StdTcpListener> for MioListener { pub trait FromStream: AsyncRead + AsyncWrite + Sized {
fn from(lst: StdTcpListener) -> Self { fn from_stdstream(sock: StdStream) -> io::Result<Self>;
MioListener::Tcp(MioTcpListener::from_std(lst))
}
} }
#[cfg(unix)]
impl From<StdUnixListener> for MioListener {
fn from(lst: StdUnixListener) -> Self {
MioListener::Uds(MioUnixListener::from_std(lst))
}
}
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),
}
}
}
impl fmt::Display for MioListener {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
MioListener::Tcp(ref lst) => write!(f, "{}", lst.local_addr().ok().unwrap()),
#[cfg(unix)]
MioListener::Uds(ref lst) => write!(f, "{:?}", lst.local_addr().ok().unwrap()),
}
}
}
pub(crate) enum SocketAddr {
Tcp(StdSocketAddr),
#[cfg(unix)]
Uds(MioSocketAddr),
}
impl fmt::Display for SocketAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
SocketAddr::Tcp(ref addr) => write!(f, "{}", addr),
#[cfg(unix)]
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
}
}
}
impl fmt::Debug for SocketAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
SocketAddr::Tcp(ref addr) => write!(f, "{:?}", addr),
#[cfg(unix)]
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
}
}
}
#[derive(Debug)]
pub enum MioStream {
Tcp(MioTcpStream),
#[cfg(unix)]
Uds(MioUnixStream),
}
/// helper trait for converting mio stream to tokio stream.
pub trait FromStream: Sized {
fn from_mio(sock: MioStream) -> io::Result<Self>;
}
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
#[cfg(unix)]
impl FromStream for TcpStream { impl FromStream for TcpStream {
fn from_mio(sock: MioStream) -> io::Result<Self> { fn from_stdstream(sock: StdStream) -> io::Result<Self> {
match sock { match sock {
MioStream::Tcp(mio) => { StdStream::Tcp(stream) => TcpStream::from_std(stream),
let raw = IntoRawFd::into_raw_fd(mio); #[cfg(all(unix))]
// SAFETY: This is a in place conversion from mio stream to tokio stream. StdStream::Uds(_) => {
TcpStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
}
MioStream::Uds(_) => {
panic!("Should not happen, bug in server impl"); panic!("Should not happen, bug in server impl");
} }
} }
} }
} }
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream #[cfg(all(unix))]
#[cfg(windows)] impl FromStream for actix_rt::net::UnixStream {
impl FromStream for TcpStream { fn from_stdstream(sock: StdStream) -> io::Result<Self> {
fn from_mio(sock: MioStream) -> io::Result<Self> {
match sock { match sock {
MioStream::Tcp(mio) => { StdStream::Tcp(_) => panic!("Should not happen, bug in server impl"),
let raw = IntoRawSocket::into_raw_socket(mio); StdStream::Uds(stream) => actix_rt::net::UnixStream::from_std(stream),
// SAFETY: This is a in place conversion from mio stream to tokio stream.
TcpStream::from_std(unsafe { FromRawSocket::from_raw_socket(raw) })
}
}
}
}
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
#[cfg(unix)]
impl FromStream for UnixStream {
fn from_mio(sock: MioStream) -> io::Result<Self> {
match sock {
MioStream::Tcp(_) => panic!("Should not happen, bug in server impl"),
MioStream::Uds(mio) => {
let raw = IntoRawFd::into_raw_fd(mio);
// SAFETY: This is a in place conversion from mio stream to tokio stream.
UnixStream::from_std(unsafe { FromRawFd::from_raw_fd(raw) })
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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");
let addr: StdSocketAddr = "127.0.0.1:0".parse().unwrap();
let socket = MioTcpSocket::new_v4().unwrap();
socket.set_reuseaddr(true).unwrap();
socket.bind(addr).unwrap();
let tcp = socket.listen(128).unwrap();
let lst = MioListener::Tcp(tcp);
assert!(format!("{:?}", lst).contains("TcpListener"));
assert!(format!("{}", lst).contains("127.0.0.1"));
}
#[test]
#[cfg(unix)]
fn uds() {
let _ = std::fs::remove_file("/tmp/sock.xxxxx");
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"));
let lst = MioListener::Uds(socket);
assert!(format!("{:?}", lst).contains("/tmp/sock.xxxxx"));
assert!(format!("{}", lst).contains("/tmp/sock.xxxxx"));
} }
} }
} }

View File

@@ -1,89 +0,0 @@
use std::{
collections::VecDeque,
ops::Deref,
sync::{Arc, Mutex, MutexGuard},
};
use mio::{Registry, Token as MioToken, Waker};
use crate::worker::WorkerHandle;
/// Waker token for `mio::Poll` instance.
pub(crate) const WAKER_TOKEN: MioToken = MioToken(usize::MAX);
/// `mio::Waker` with a queue for waking up the `Accept`'s `Poll` and contains the `WakerInterest`
/// the `Poll` would want to look into.
pub(crate) struct WakerQueue(Arc<(Waker, Mutex<VecDeque<WakerInterest>>)>);
impl Clone for WakerQueue {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Deref for WakerQueue {
type Target = (Waker, Mutex<VecDeque<WakerInterest>>);
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}
impl WakerQueue {
/// Construct a waker queue with given `Poll`'s `Registry` and capacity.
///
/// A fixed `WAKER_TOKEN` is used to identify the wake interest and the `Poll` needs to match
/// event's token for it to properly handle `WakerInterest`.
pub(crate) fn new(registry: &Registry) -> std::io::Result<Self> {
let waker = Waker::new(registry, WAKER_TOKEN)?;
let queue = Mutex::new(VecDeque::with_capacity(16));
Ok(Self(Arc::new((waker, queue))))
}
/// Push a new interest to the queue and wake up the accept poll afterwards.
pub(crate) fn wake(&self, interest: WakerInterest) {
let (waker, queue) = self.deref();
queue
.lock()
.expect("Failed to lock WakerQueue")
.push_back(interest);
waker
.wake()
.unwrap_or_else(|e| panic!("can not wake up Accept Poll: {}", e));
}
/// Get a MutexGuard of the waker queue.
pub(crate) fn guard(&self) -> MutexGuard<'_, VecDeque<WakerInterest>> {
self.deref().1.lock().expect("Failed to lock WakerQueue")
}
/// Reset the waker queue so it does not grow infinitely.
pub(crate) fn reset(queue: &mut VecDeque<WakerInterest>) {
std::mem::swap(&mut VecDeque::<WakerInterest>::with_capacity(16), queue);
}
}
/// Types of interests we would look into when `Accept`'s `Poll` is waked up by waker.
///
/// These interests should not be confused with `mio::Interest` and mostly not I/O related
pub(crate) enum WakerInterest {
/// `WorkerAvailable` is an interest from `Worker` notifying `Accept` there is a worker
/// available and can accept new tasks.
WorkerAvailable,
/// `Pause`, `Resume`, `Stop` Interest are from `ServerBuilder` future. It listens to
/// `ServerCommand` and notify `Accept` to do exactly these tasks.
Pause,
Resume,
Stop,
/// `Timer` is an interest sent as a delayed future. When an error happens on accepting
/// connection `Accept` would deregister socket listener temporary and wake up the poll and
/// register them again after the delayed future resolve.
Timer,
/// `Worker` is an interest happen after a worker runs into faulted state(This is determined
/// by if work can be sent to it successfully).`Accept` would be waked up and add the new
/// `WorkerHandle`.
Worker(WorkerHandle),
}

View File

@@ -1,22 +1,22 @@
use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time::Duration; use std::time;
use actix_rt::time::{sleep, Sleep}; use actix_rt::time::{delay_until, Delay, Instant};
use actix_rt::{spawn, Arbiter}; use actix_rt::{spawn, Arbiter};
use actix_utils::counter::Counter; use actix_utils::counter::Counter;
use futures_core::future::LocalBoxFuture; use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures_channel::oneshot;
use futures_util::future::{join_all, LocalBoxFuture, MapOk};
use futures_util::{future::Future, stream::Stream, FutureExt, TryFutureExt};
use log::{error, info, trace}; use log::{error, info, trace};
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use tokio::sync::oneshot;
use crate::accept::AcceptNotify;
use crate::service::{BoxedServerService, InternalServiceFactory}; use crate::service::{BoxedServerService, InternalServiceFactory};
use crate::socket::{MioStream, SocketAddr}; use crate::socket::{SocketAddr, StdStream};
use crate::waker_queue::{WakerInterest, WakerQueue}; use crate::Token;
use crate::{join_all, Token};
pub(crate) struct WorkerCommand(Conn); pub(crate) struct WorkerCommand(Conn);
@@ -29,7 +29,7 @@ pub(crate) struct StopCommand {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Conn { pub(crate) struct Conn {
pub io: MioStream, pub io: StdStream,
pub token: Token, pub token: Token,
pub peer: Option<SocketAddr>, pub peer: Option<SocketAddr>,
} }
@@ -46,33 +46,31 @@ pub fn max_concurrent_connections(num: usize) {
MAX_CONNS.store(num, Ordering::Relaxed); MAX_CONNS.store(num, Ordering::Relaxed);
} }
pub(crate) fn num_connections() -> usize {
MAX_CONNS_COUNTER.with(|conns| conns.total())
}
thread_local! { thread_local! {
static MAX_CONNS_COUNTER: Counter = static MAX_CONNS_COUNTER: Counter =
Counter::new(MAX_CONNS.load(Ordering::Relaxed)); Counter::new(MAX_CONNS.load(Ordering::Relaxed));
} }
pub(crate) fn num_connections() -> usize {
MAX_CONNS_COUNTER.with(|conns| conns.total())
}
// a handle to worker that can send message to worker and share the availability of worker to other
// thread.
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct WorkerHandle { pub(crate) struct WorkerClient {
pub idx: usize, pub idx: usize,
tx1: UnboundedSender<WorkerCommand>, tx1: UnboundedSender<WorkerCommand>,
tx2: UnboundedSender<StopCommand>, tx2: UnboundedSender<StopCommand>,
avail: WorkerAvailability, avail: WorkerAvailability,
} }
impl WorkerHandle { impl WorkerClient {
pub fn new( pub fn new(
idx: usize, idx: usize,
tx1: UnboundedSender<WorkerCommand>, tx1: UnboundedSender<WorkerCommand>,
tx2: UnboundedSender<StopCommand>, tx2: UnboundedSender<StopCommand>,
avail: WorkerAvailability, avail: WorkerAvailability,
) -> Self { ) -> Self {
WorkerHandle { WorkerClient {
idx, idx,
tx1, tx1,
tx2, tx2,
@@ -81,7 +79,9 @@ impl WorkerHandle {
} }
pub fn send(&self, msg: Conn) -> Result<(), Conn> { pub fn send(&self, msg: Conn) -> Result<(), Conn> {
self.tx1.send(WorkerCommand(msg)).map_err(|msg| msg.0 .0) self.tx1
.unbounded_send(WorkerCommand(msg))
.map_err(|msg| msg.into_inner().0)
} }
pub fn available(&self) -> bool { pub fn available(&self) -> bool {
@@ -90,21 +90,21 @@ impl WorkerHandle {
pub fn stop(&self, graceful: bool) -> oneshot::Receiver<bool> { pub fn stop(&self, graceful: bool) -> oneshot::Receiver<bool> {
let (result, rx) = oneshot::channel(); let (result, rx) = oneshot::channel();
let _ = self.tx2.send(StopCommand { graceful, result }); let _ = self.tx2.unbounded_send(StopCommand { graceful, result });
rx rx
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct WorkerAvailability { pub(crate) struct WorkerAvailability {
waker: WakerQueue, notify: AcceptNotify,
available: Arc<AtomicBool>, available: Arc<AtomicBool>,
} }
impl WorkerAvailability { impl WorkerAvailability {
pub fn new(waker: WakerQueue) -> Self { pub fn new(notify: AcceptNotify) -> Self {
WorkerAvailability { WorkerAvailability {
waker, notify,
available: Arc::new(AtomicBool::new(false)), available: Arc::new(AtomicBool::new(false)),
} }
} }
@@ -115,17 +115,17 @@ impl WorkerAvailability {
pub fn set(&self, val: bool) { pub fn set(&self, val: bool) {
let old = self.available.swap(val, Ordering::Release); let old = self.available.swap(val, Ordering::Release);
// notify the accept on switched to available.
if !old && val { if !old && val {
self.waker.wake(WakerInterest::WorkerAvailable); self.notify.notify()
} }
} }
} }
/// Service worker. /// Service worker
/// ///
/// Worker accepts Socket objects via unbounded channel and starts stream processing. /// Worker accepts Socket objects via unbounded channel and starts stream
pub(crate) struct ServerWorker { /// processing.
pub(crate) struct Worker {
rx: UnboundedReceiver<WorkerCommand>, rx: UnboundedReceiver<WorkerCommand>,
rx2: UnboundedReceiver<StopCommand>, rx2: UnboundedReceiver<StopCommand>,
services: Vec<WorkerService>, services: Vec<WorkerService>,
@@ -133,7 +133,7 @@ pub(crate) struct ServerWorker {
conns: Counter, conns: Counter,
factories: Vec<Box<dyn InternalServiceFactory>>, factories: Vec<Box<dyn InternalServiceFactory>>,
state: WorkerState, state: WorkerState,
config: ServerWorkerConfig, shutdown_timeout: time::Duration,
} }
struct WorkerService { struct WorkerService {
@@ -159,108 +159,68 @@ enum WorkerServiceStatus {
Stopped, Stopped,
} }
/// Config for worker behavior passed down from server builder. impl Worker {
#[derive(Copy, Clone)]
pub(crate) struct ServerWorkerConfig {
shutdown_timeout: Duration,
max_blocking_threads: usize,
}
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(), 1);
Self {
shutdown_timeout: Duration::from_secs(30),
max_blocking_threads,
}
}
}
impl ServerWorkerConfig {
pub(crate) fn max_blocking_threads(&mut self, num: usize) {
self.max_blocking_threads = num;
}
pub(crate) fn shutdown_timeout(&mut self, dur: Duration) {
self.shutdown_timeout = dur;
}
}
impl ServerWorker {
pub(crate) fn start( pub(crate) fn start(
idx: usize, idx: usize,
factories: Vec<Box<dyn InternalServiceFactory>>, factories: Vec<Box<dyn InternalServiceFactory>>,
availability: WorkerAvailability, availability: WorkerAvailability,
config: ServerWorkerConfig, shutdown_timeout: time::Duration,
) -> WorkerHandle { ) -> WorkerClient {
let (tx1, rx) = unbounded_channel(); let (tx1, rx) = unbounded();
let (tx2, rx2) = unbounded_channel(); let (tx2, rx2) = unbounded();
let avail = availability.clone(); let avail = availability.clone();
// every worker runs in it's own arbiter. Arbiter::new().send(
// use a custom tokio runtime builder to change the settings of runtime. async move {
Arbiter::with_tokio_rt(move || { availability.set(false);
tokio::runtime::Builder::new_current_thread() let mut wrk = MAX_CONNS_COUNTER.with(move |conns| Worker {
.enable_all() rx,
.max_blocking_threads(config.max_blocking_threads) rx2,
.build() availability,
.unwrap() factories,
}) shutdown_timeout,
.spawn(async move { services: Vec::new(),
availability.set(false); conns: conns.clone(),
let mut wrk = MAX_CONNS_COUNTER.with(move |conns| ServerWorker { state: WorkerState::Unavailable(Vec::new()),
rx, });
rx2,
availability,
factories,
config,
services: Vec::new(),
conns: conns.clone(),
state: WorkerState::Unavailable,
});
let fut = wrk let mut fut: Vec<MapOk<LocalBoxFuture<'static, _>, _>> = Vec::new();
.factories for (idx, factory) in wrk.factories.iter().enumerate() {
.iter() fut.push(factory.create().map_ok(move |r| {
.enumerate() r.into_iter()
.map(|(idx, factory)| { .map(|(t, s): (Token, _)| (idx, t, s))
let fut = factory.create(); .collect::<Vec<_>>()
async move { }));
fut.await.map(|r| { }
r.into_iter().map(|(t, s)| (idx, t, s)).collect::<Vec<_>>()
})
}
})
.collect::<Vec<_>>();
// a second spawn to make sure worker future runs as non boxed future. spawn(async move {
// As Arbiter::spawn would box the future before send it to arbiter. let res = join_all(fut).await;
spawn(async move { let res: Result<Vec<_>, _> = res.into_iter().collect();
let res: Result<Vec<_>, _> = join_all(fut).await.into_iter().collect(); match res {
match res { Ok(services) => {
Ok(services) => { for item in services {
for item in services { for (factory, token, service) in item {
for (factory, token, service) in item { assert_eq!(token.0, wrk.services.len());
assert_eq!(token.0, wrk.services.len()); wrk.services.push(WorkerService {
wrk.services.push(WorkerService { factory,
factory, service,
service, status: WorkerServiceStatus::Unavailable,
status: WorkerServiceStatus::Unavailable, });
}); }
} }
} }
Err(e) => {
error!("Can not start worker: {:?}", e);
Arbiter::current().stop();
}
} }
Err(e) => { wrk.await
error!("Can not start worker: {:?}", e); });
Arbiter::current().stop(); }
} .boxed(),
} );
wrk.await
});
});
WorkerHandle::new(idx, tx1, tx2, avail) WorkerClient::new(idx, tx1, tx2, avail)
} }
fn shutdown(&mut self, force: bool) { fn shutdown(&mut self, force: bool) {
@@ -282,7 +242,7 @@ impl ServerWorker {
fn check_readiness(&mut self, cx: &mut Context<'_>) -> Result<bool, (Token, usize)> { fn check_readiness(&mut self, cx: &mut Context<'_>) -> Result<bool, (Token, usize)> {
let mut ready = self.conns.available(cx); let mut ready = self.conns.available(cx);
let mut failed = None; let mut failed = None;
for (idx, srv) in self.services.iter_mut().enumerate() { for (idx, srv) in &mut self.services.iter_mut().enumerate() {
if srv.status == WorkerServiceStatus::Available if srv.status == WorkerServiceStatus::Available
|| srv.status == WorkerServiceStatus::Unavailable || srv.status == WorkerServiceStatus::Unavailable
{ {
@@ -328,26 +288,29 @@ impl ServerWorker {
enum WorkerState { enum WorkerState {
Available, Available,
Unavailable, Unavailable(Vec<Conn>),
Restarting( Restarting(
usize, usize,
Token, Token,
LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>>, #[allow(clippy::type_complexity)]
Pin<Box<dyn Future<Output = Result<Vec<(Token, BoxedServerService)>, ()>>>>,
), ),
Shutdown( Shutdown(
Pin<Box<Sleep>>, Pin<Box<Delay>>,
Pin<Box<Sleep>>, Pin<Box<Delay>>,
Option<oneshot::Sender<bool>>, Option<oneshot::Sender<bool>>,
), ),
} }
impl Future for ServerWorker { impl Future for Worker {
type Output = (); type Output = ();
// FIXME: remove this attribute
#[allow(clippy::never_loop)]
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// `StopWorker` message handler // `StopWorker` message handler
if let Poll::Ready(Some(StopCommand { graceful, result })) = if let Poll::Ready(Some(StopCommand { graceful, result })) =
Pin::new(&mut self.rx2).poll_recv(cx) Pin::new(&mut self.rx2).poll_next(cx)
{ {
self.availability.set(false); self.availability.set(false);
let num = num_connections(); let num = num_connections();
@@ -361,8 +324,8 @@ impl Future for ServerWorker {
if num != 0 { if num != 0 {
info!("Graceful worker shutdown, {} connections", num); info!("Graceful worker shutdown, {} connections", num);
self.state = WorkerState::Shutdown( self.state = WorkerState::Shutdown(
Box::pin(sleep(Duration::from_secs(1))), Box::pin(delay_until(Instant::now() + time::Duration::from_secs(1))),
Box::pin(sleep(self.config.shutdown_timeout)), Box::pin(delay_until(Instant::now() + self.shutdown_timeout)),
Some(result), Some(result),
); );
} else { } else {
@@ -378,35 +341,53 @@ impl Future for ServerWorker {
} }
match self.state { match self.state {
WorkerState::Unavailable => match self.check_readiness(cx) { WorkerState::Unavailable(ref mut conns) => {
Ok(true) => { let conn = conns.pop();
self.state = WorkerState::Available; match self.check_readiness(cx) {
self.availability.set(true); Ok(true) => {
self.poll(cx) // process requests from wait queue
if let Some(conn) = conn {
let guard = self.conns.get();
let _ = self.services[conn.token.0]
.service
.call((Some(guard), conn.io));
} else {
self.state = WorkerState::Available;
self.availability.set(true);
}
self.poll(cx)
}
Ok(false) => {
// push connection back to queue
if let Some(conn) = conn {
if let WorkerState::Unavailable(ref mut conns) = self.state {
conns.push(conn);
}
}
Poll::Pending
}
Err((token, idx)) => {
trace!(
"Service {:?} failed, restarting",
self.factories[idx].name(token)
);
self.services[token.0].status = WorkerServiceStatus::Restarting;
self.state =
WorkerState::Restarting(idx, token, self.factories[idx].create());
self.poll(cx)
}
} }
Ok(false) => Poll::Pending, }
Err((token, idx)) => {
trace!(
"Service {:?} failed, restarting",
self.factories[idx].name(token)
);
self.services[token.0].status = WorkerServiceStatus::Restarting;
self.state =
WorkerState::Restarting(idx, token, self.factories[idx].create());
self.poll(cx)
}
},
WorkerState::Restarting(idx, token, ref mut fut) => { WorkerState::Restarting(idx, token, ref mut fut) => {
match fut.as_mut().poll(cx) { match Pin::new(fut).poll(cx) {
Poll::Ready(Ok(item)) => { Poll::Ready(Ok(item)) => {
// only interest in the first item? for (token, service) in item {
if let Some((token, service)) = item.into_iter().next() {
trace!( trace!(
"Service {:?} has been restarted", "Service {:?} has been restarted",
self.factories[idx].name(token) self.factories[idx].name(token)
); );
self.services[token.0].created(service); self.services[token.0].created(service);
self.state = WorkerState::Unavailable; self.state = WorkerState::Unavailable(Vec::new());
return self.poll(cx); return self.poll(cx);
} }
} }
@@ -416,7 +397,9 @@ impl Future for ServerWorker {
self.factories[idx].name(token) self.factories[idx].name(token)
); );
} }
Poll::Pending => return Poll::Pending, Poll::Pending => {
return Poll::Pending;
}
} }
self.poll(cx) self.poll(cx)
} }
@@ -429,56 +412,71 @@ impl Future for ServerWorker {
} }
// check graceful timeout // check graceful timeout
if Pin::new(t2).poll(cx).is_ready() { match t2.as_mut().poll(cx) {
let _ = tx.take().unwrap().send(false); Poll::Pending => (),
self.shutdown(true); Poll::Ready(_) => {
Arbiter::current().stop(); let _ = tx.take().unwrap().send(false);
return Poll::Ready(()); self.shutdown(true);
Arbiter::current().stop();
return Poll::Ready(());
}
} }
// sleep for 1 second and then check again // sleep for 1 second and then check again
if t1.as_mut().poll(cx).is_ready() { match t1.as_mut().poll(cx) {
*t1 = Box::pin(sleep(Duration::from_secs(1))); Poll::Pending => (),
let _ = t1.as_mut().poll(cx); Poll::Ready(_) => {
*t1 = Box::pin(delay_until(
Instant::now() + time::Duration::from_secs(1),
));
let _ = t1.as_mut().poll(cx);
}
} }
Poll::Pending Poll::Pending
} }
// actively poll stream and handle worker command WorkerState::Available => {
WorkerState::Available => loop { loop {
match self.check_readiness(cx) { match Pin::new(&mut self.rx).poll_next(cx) {
Ok(true) => (), // handle incoming io stream
Ok(false) => { Poll::Ready(Some(WorkerCommand(msg))) => {
trace!("Worker is unavailable"); match self.check_readiness(cx) {
self.availability.set(false); Ok(true) => {
self.state = WorkerState::Unavailable; let guard = self.conns.get();
return self.poll(cx); let _ = self.services[msg.token.0]
} .service
Err((token, idx)) => { .call((Some(guard), msg.io));
trace!( continue;
"Service {:?} failed, restarting", }
self.factories[idx].name(token) Ok(false) => {
); trace!("Worker is unavailable");
self.availability.set(false); self.availability.set(false);
self.services[token.0].status = WorkerServiceStatus::Restarting; self.state = WorkerState::Unavailable(vec![msg]);
self.state = }
WorkerState::Restarting(idx, token, self.factories[idx].create()); Err((token, idx)) => {
return self.poll(cx); trace!(
"Service {:?} failed, restarting",
self.factories[idx].name(token)
);
self.availability.set(false);
self.services[token.0].status =
WorkerServiceStatus::Restarting;
self.state = WorkerState::Restarting(
idx,
token,
self.factories[idx].create(),
);
}
}
return self.poll(cx);
}
Poll::Pending => {
self.state = WorkerState::Available;
return Poll::Pending;
}
Poll::Ready(None) => return Poll::Ready(()),
} }
} }
}
match Pin::new(&mut self.rx).poll_recv(cx) {
// handle incoming io stream
Poll::Ready(Some(WorkerCommand(msg))) => {
let guard = self.conns.get();
let _ = self.services[msg.token.0]
.service
.call((Some(guard), msg.io));
}
Poll::Pending => return Poll::Pending,
Poll::Ready(None) => return Poll::Ready(()),
};
},
} }
} }
} }

View File

@@ -5,13 +5,14 @@ use std::{net, thread, time};
use actix_server::Server; use actix_server::Server;
use actix_service::fn_service; use actix_service::fn_service;
use futures_util::future::{lazy, ok}; use futures_util::future::{lazy, ok};
use socket2::{Domain, Protocol, Socket, Type};
fn unused_addr() -> net::SocketAddr { fn unused_addr() -> net::SocketAddr {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
let socket = mio::net::TcpSocket::new_v4().unwrap(); let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
socket.bind(addr).unwrap(); socket.bind(&addr.into()).unwrap();
socket.set_reuseaddr(true).unwrap(); socket.set_reuse_address(true).unwrap();
let tcp = socket.listen(32).unwrap(); let tcp = socket.into_tcp_listener();
tcp.local_addr().unwrap() tcp.local_addr().unwrap()
} }
@@ -21,15 +22,13 @@ fn test_bind() {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let h = thread::spawn(move || { let h = thread::spawn(move || {
let sys = actix_rt::System::new(); let sys = actix_rt::System::new("test");
let srv = sys.block_on(lazy(|_| { let srv = Server::build()
Server::build() .workers(1)
.workers(1) .disable_signals()
.disable_signals() .bind("test", addr, move || fn_service(|_| ok::<_, ()>(())))
.bind("test", addr, move || fn_service(|_| ok::<_, ()>(()))) .unwrap()
.unwrap() .start();
.run()
}));
let _ = tx.send((srv, actix_rt::System::current())); let _ = tx.send((srv, actix_rt::System::current()));
let _ = sys.run(); let _ = sys.run();
}); });
@@ -47,17 +46,15 @@ fn test_listen() {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let h = thread::spawn(move || { let h = thread::spawn(move || {
let sys = actix_rt::System::new(); let sys = actix_rt::System::new("test");
let lst = net::TcpListener::bind(addr).unwrap(); let lst = net::TcpListener::bind(addr).unwrap();
sys.block_on(async { Server::build()
Server::build() .disable_signals()
.disable_signals() .workers(1)
.workers(1) .listen("test", lst, move || fn_service(|_| ok::<_, ()>(())))
.listen("test", lst, move || fn_service(|_| ok::<_, ()>(()))) .unwrap()
.unwrap() .start();
.run(); let _ = tx.send(actix_rt::System::current());
let _ = tx.send(actix_rt::System::current());
});
let _ = sys.run(); let _ = sys.run();
}); });
let sys = rx.recv().unwrap(); let sys = rx.recv().unwrap();
@@ -81,26 +78,23 @@ fn test_start() {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let h = thread::spawn(move || { let h = thread::spawn(move || {
let sys = actix_rt::System::new(); let sys = actix_rt::System::new("test");
let srv = sys.block_on(lazy(|_| { let srv: Server = Server::build()
Server::build() .backlog(100)
.backlog(100) .disable_signals()
.disable_signals() .bind("test", addr, move || {
.bind("test", addr, move || { fn_service(|io: TcpStream| async move {
fn_service(|io: TcpStream| async move { let mut f = Framed::new(io, BytesCodec);
let mut f = Framed::new(io, BytesCodec); f.send(Bytes::from_static(b"test")).await.unwrap();
f.send(Bytes::from_static(b"test")).await.unwrap(); Ok::<_, ()>(())
Ok::<_, ()>(())
})
}) })
.unwrap() })
.run() .unwrap()
})); .start();
let _ = tx.send((srv, actix_rt::System::current())); let _ = tx.send((srv, actix_rt::System::current()));
let _ = sys.run(); let _ = sys.run();
}); });
let (srv, sys) = rx.recv().unwrap(); let (srv, sys) = rx.recv().unwrap();
let mut buf = [1u8; 4]; let mut buf = [1u8; 4];
@@ -150,31 +144,29 @@ fn test_configure() {
let h = thread::spawn(move || { let h = thread::spawn(move || {
let num = num2.clone(); let num = num2.clone();
let sys = actix_rt::System::new(); let sys = actix_rt::System::new("test");
let srv = sys.block_on(lazy(|_| { let srv = Server::build()
Server::build() .disable_signals()
.disable_signals() .configure(move |cfg| {
.configure(move |cfg| { let num = num.clone();
let num = num.clone(); let lst = net::TcpListener::bind(addr3).unwrap();
let lst = net::TcpListener::bind(addr3).unwrap(); cfg.bind("addr1", addr1)
cfg.bind("addr1", addr1) .unwrap()
.unwrap() .bind("addr2", addr2)
.bind("addr2", addr2) .unwrap()
.unwrap() .listen("addr3", lst)
.listen("addr3", lst) .apply(move |rt| {
.apply(move |rt| { let num = num.clone();
let num = num.clone(); rt.service("addr1", fn_service(|_| ok::<_, ()>(())));
rt.service("addr1", fn_service(|_| ok::<_, ()>(()))); rt.service("addr3", fn_service(|_| ok::<_, ()>(())));
rt.service("addr3", fn_service(|_| ok::<_, ()>(()))); rt.on_start(lazy(move |_| {
rt.on_start(lazy(move |_| { let _ = num.fetch_add(1, Relaxed);
let _ = num.fetch_add(1, Relaxed); }))
})) })
}) })
}) .unwrap()
.unwrap() .workers(1)
.workers(1) .start();
.run()
}));
let _ = tx.send((srv, actix_rt::System::current())); let _ = tx.send((srv, actix_rt::System::current()));
let _ = sys.run(); let _ = sys.run();
}); });

View File

@@ -1,58 +1,13 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2020-xx-xx
* Upgrade `pin-project` to `1.0`.
## 2.0.0-beta.5 - 2021-03-15
* Add default `Service` trait impl for `Rc<S: Service>` 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 - 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]
* `fn_service` and friends now receive `Fn(Req)` function type. [#247]
[#247]: https://github.com/actix/actix-net/pull/247
## 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 - 2021-01-03
* Remove redundant type parameter from `map_config`.
## 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]
* 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]
* 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 - 2020-08-09 ## 1.0.6 - 2020-08-09
### Fixed ### Fixed
* Removed unsound custom Cell implementation that allowed obtaining several mutable references to * 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.
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] - 2020-01-16 ## [1.0.5] - 2020-01-16

View File

@@ -1,11 +1,7 @@
[package] [package]
name = "actix-service" name = "actix-service"
version = "2.0.0-beta.5" version = "1.0.6"
authors = [ authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
"fakeshadow <24548779@qq.com>",
]
description = "Service trait and combinators for representing asynchronous request/response operations." description = "Service trait and combinators for representing asynchronous request/response operations."
keywords = ["network", "framework", "async", "futures", "service"] keywords = ["network", "framework", "async", "futures", "service"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
@@ -21,9 +17,17 @@ name = "actix_service"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
futures-core = { version = "0.3.7", default-features = false } futures-util = "0.3.1"
pin-project-lite = "0.2" pin-project = "1.0.0"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.0.0" actix-rt = "1.0.0"
futures-util = { version = "0.3.7", default-features = false } criterion = "0.3"
[[bench]]
name = "unsafecell_vs_refcell"
harness = false
[[bench]]
name = "and_then"
harness = false

View File

@@ -2,12 +2,6 @@
> Service trait and combinators for representing asynchronous request/response operations. > 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) See documentation for detailed explanations these components: [https://docs.rs/actix-service][docs].
[![Documentation](https://docs.rs/actix-service/badge.svg?version=2.0.0-beta.5)](https://docs.rs/actix-service/2.0.0-beta.5)
[![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.0-beta.5/status.svg)](https://deps.rs/crate/actix-service/2.0.0-beta.5)
![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)
See documentation for detailed explanations of these components: https://docs.rs/actix-service. [docs]: https://docs.rs/actix-service

View File

@@ -0,0 +1,332 @@
use actix_service::boxed::BoxFuture;
use actix_service::IntoService;
use actix_service::Service;
/// Benchmark various implementations of and_then
use criterion::{criterion_main, Criterion};
use futures_util::future::join_all;
use futures_util::future::TryFutureExt;
use std::cell::{RefCell, UnsafeCell};
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
/*
* Test services A,B for AndThen service implementations
*/
async fn svc1(_: ()) -> Result<usize, ()> {
Ok(1)
}
async fn svc2(req: usize) -> Result<usize, ()> {
Ok(req + 1)
}
/*
* AndThenUC - original AndThen service based on UnsafeCell
* Cut down version of actix_service::AndThenService based on actix-service::Cell
*/
struct AndThenUC<A, B>(Rc<UnsafeCell<(A, B)>>);
impl<A, B> AndThenUC<A, B> {
fn new(a: A, b: B) -> Self
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self(Rc::new(UnsafeCell::new((a, b))))
}
}
impl<A, B> Clone for AndThenUC<A, B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<A, B> Service for AndThenUC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Request = A::Request;
type Response = B::Response;
type Error = A::Error;
type Future = AndThenServiceResponse<A, B>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = unsafe { &mut *(*self.0).get() }.0.call(req);
AndThenServiceResponse {
state: State::A(fut, Some(self.0.clone())),
}
}
}
#[pin_project::pin_project]
pub(crate) struct AndThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
#[pin]
state: State<A, B>,
}
#[pin_project::pin_project(project = StateProj)]
enum State<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
A(#[pin] A::Future, Option<Rc<UnsafeCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
impl<A, B> Future for AndThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Output = Result<B::Response, A::Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
StateProj::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let b = b.take().unwrap();
this.state.set(State::Empty); // drop fut A
let fut = unsafe { &mut (*b.get()).1 }.call(res);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
}
}
}
/*
* AndThenRC - AndThen service based on RefCell
*/
struct AndThenRC<A, B>(Rc<RefCell<(A, B)>>);
impl<A, B> AndThenRC<A, B> {
fn new(a: A, b: B) -> Self
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self(Rc::new(RefCell::new((a, b))))
}
}
impl<A, B> Clone for AndThenRC<A, B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<A, B> Service for AndThenRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Request = A::Request;
type Response = B::Response;
type Error = A::Error;
type Future = AndThenServiceResponseRC<A, B>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = self.0.borrow_mut().0.call(req);
AndThenServiceResponseRC {
state: StateRC::A(fut, Some(self.0.clone())),
}
}
}
#[pin_project::pin_project]
pub(crate) struct AndThenServiceResponseRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
#[pin]
state: StateRC<A, B>,
}
#[pin_project::pin_project(project = StateRCProj)]
enum StateRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
A(#[pin] A::Future, Option<Rc<RefCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
impl<A, B> Future for AndThenServiceResponseRC<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
type Output = Result<B::Response, A::Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
StateRCProj::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let b = b.take().unwrap();
this.state.set(StateRC::Empty); // drop fut A
let fut = b.borrow_mut().1.call(res);
this.state.set(StateRC::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateRCProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(StateRC::Empty);
r
}),
StateRCProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
}
}
}
/*
* AndThenRCFuture - AndThen service based on RefCell
* and standard futures::future::and_then combinator in a Box
*/
struct AndThenRCFuture<A, B>(Rc<RefCell<(A, B)>>);
impl<A, B> AndThenRCFuture<A, B> {
fn new(a: A, b: B) -> Self
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
Self(Rc::new(RefCell::new((a, b))))
}
}
impl<A, B> Clone for AndThenRCFuture<A, B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<A, B> Service for AndThenRCFuture<A, B>
where
A: Service + 'static,
A::Future: 'static,
B: Service<Request = A::Response, Error = A::Error> + 'static,
B::Future: 'static,
{
type Request = A::Request;
type Response = B::Response;
type Error = A::Error;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = self.0.borrow_mut().0.call(req);
let core = self.0.clone();
let fut2 = move |res| (*core).borrow_mut().1.call(res);
Box::pin(fut.and_then(fut2))
}
}
/// Criterion Benchmark for async Service
/// Should be used from within criterion group:
/// ```rust,ignore
/// let mut criterion: ::criterion::Criterion<_> =
/// ::criterion::Criterion::default().configure_from_args();
/// bench_async_service(&mut criterion, ok_service(), "async_service_direct");
/// ```
///
/// Usable for benching Service wrappers:
/// Using minimum service code implementation we first measure
/// time to run minimum service, then measure time with wrapper.
///
/// Sample output
/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us]
pub fn bench_async_service<S>(c: &mut Criterion, srv: S, name: &str)
where
S: Service<Request = (), Response = usize, Error = ()> + Clone + 'static,
{
let mut rt = actix_rt::System::new("test");
// start benchmark loops
c.bench_function(name, move |b| {
b.iter_custom(|iters| {
let mut srvs: Vec<_> = (1..iters).map(|_| srv.clone()).collect();
// exclude request generation, it appears it takes significant time vs call (3us vs 1us)
let start = std::time::Instant::now();
// benchmark body
rt.block_on(async move { join_all(srvs.iter_mut().map(|srv| srv.call(()))).await });
// check that at least first request succeeded
start.elapsed()
})
});
}
pub fn service_benches() {
let mut criterion: ::criterion::Criterion<_> =
::criterion::Criterion::default().configure_from_args();
bench_async_service(
&mut criterion,
AndThenUC::new(svc1.into_service(), svc2.into_service()),
"AndThen with UnsafeCell",
);
bench_async_service(
&mut criterion,
AndThenRC::new(svc1.into_service(), svc2.into_service()),
"AndThen with RefCell",
);
bench_async_service(
&mut criterion,
AndThenUC::new(svc1.into_service(), svc2.into_service()),
"AndThen with UnsafeCell",
);
bench_async_service(
&mut criterion,
AndThenRC::new(svc1.into_service(), svc2.into_service()),
"AndThen with RefCell",
);
bench_async_service(
&mut criterion,
AndThenRCFuture::new(svc1.into_service(), svc2.into_service()),
"AndThen with RefCell via future::and_then",
);
}
criterion_main!(service_benches);

View File

@@ -0,0 +1,112 @@
use actix_service::Service;
use criterion::{criterion_main, Criterion};
use futures_util::future::join_all;
use futures_util::future::{ok, Ready};
use std::cell::{RefCell, UnsafeCell};
use std::rc::Rc;
use std::task::{Context, Poll};
struct SrvUC(Rc<UnsafeCell<usize>>);
impl Default for SrvUC {
fn default() -> Self {
Self(Rc::new(UnsafeCell::new(0)))
}
}
impl Clone for SrvUC {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Service for SrvUC {
type Request = ();
type Response = usize;
type Error = ();
type Future = Ready<Result<Self::Response, ()>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: ()) -> Self::Future {
unsafe { *(*self.0).get() = *(*self.0).get() + 1 };
ok(unsafe { *self.0.get() })
}
}
struct SrvRC(Rc<RefCell<usize>>);
impl Default for SrvRC {
fn default() -> Self {
Self(Rc::new(RefCell::new(0)))
}
}
impl Clone for SrvRC {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Service for SrvRC {
type Request = ();
type Response = usize;
type Error = ();
type Future = Ready<Result<Self::Response, ()>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: ()) -> Self::Future {
let prev = *self.0.borrow();
*(*self.0).borrow_mut() = prev + 1;
ok(*self.0.borrow())
}
}
/// Criterion Benchmark for async Service
/// Should be used from within criterion group:
/// ```rust,ignore
/// let mut criterion: ::criterion::Criterion<_> =
/// ::criterion::Criterion::default().configure_from_args();
/// bench_async_service(&mut criterion, ok_service(), "async_service_direct");
/// ```
///
/// Usable for benching Service wrappers:
/// Using minimum service code implementation we first measure
/// time to run minimum service, then measure time with wrapper.
///
/// Sample output
/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us]
pub fn bench_async_service<S>(c: &mut Criterion, srv: S, name: &str)
where
S: Service<Request = (), Response = usize, Error = ()> + Clone + 'static,
{
let mut rt = actix_rt::System::new("test");
// start benchmark loops
c.bench_function(name, move |b| {
b.iter_custom(|iters| {
let mut srvs: Vec<_> = (1..iters).map(|_| srv.clone()).collect();
// exclude request generation, it appears it takes significant time vs call (3us vs 1us)
let start = std::time::Instant::now();
// benchmark body
rt.block_on(async move { join_all(srvs.iter_mut().map(|srv| srv.call(()))).await });
// check that at least first request succeeded
start.elapsed()
})
});
}
pub fn service_benches() {
let mut criterion: ::criterion::Criterion<_> =
::criterion::Criterion::default().configure_from_args();
bench_async_service(&mut criterion, SrvUC::default(), "Service with UnsafeCell");
bench_async_service(&mut criterion, SrvRC::default(), "Service with RefCell");
bench_async_service(&mut criterion, SrvUC::default(), "Service with UnsafeCell");
bench_async_service(&mut criterion, SrvRC::default(), "Service with RefCell");
}
criterion_main!(service_benches);

View File

@@ -1,13 +1,8 @@
use alloc::rc::Rc; use std::cell::RefCell;
use core::{ use std::future::Future;
future::Future, use std::pin::Pin;
marker::PhantomData, use std::rc::Rc;
pin::Pin, use std::task::{Context, Poll};
task::{Context, Poll},
};
use futures_core::ready;
use pin_project_lite::pin_project;
use super::{Service, ServiceFactory}; use super::{Service, ServiceFactory};
@@ -15,88 +10,77 @@ use super::{Service, ServiceFactory};
/// of another service which completes successfully. /// of another service which completes successfully.
/// ///
/// This is created by the `Pipeline::and_then` method. /// This is created by the `Pipeline::and_then` method.
pub(crate) struct AndThenService<A, B, Req>(Rc<(A, B)>, PhantomData<Req>); pub(crate) struct AndThenService<A, B>(Rc<RefCell<(A, B)>>);
impl<A, B, Req> AndThenService<A, B, Req> { impl<A, B> AndThenService<A, B> {
/// Create new `AndThen` combinator /// Create new `AndThen` combinator
pub(crate) fn new(a: A, b: B) -> Self pub(crate) fn new(a: A, b: B) -> Self
where where
A: Service<Req>, A: Service,
B: Service<A::Response, Error = A::Error>, B: Service<Request = A::Response, Error = A::Error>,
{ {
Self(Rc::new((a, b)), PhantomData) Self(Rc::new(RefCell::new((a, b))))
} }
} }
impl<A, B, Req> Clone for AndThenService<A, B, Req> { impl<A, B> Clone for AndThenService<A, B> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
AndThenService(self.0.clone(), PhantomData) AndThenService(self.0.clone())
} }
} }
impl<A, B, Req> Service<Req> for AndThenService<A, B, Req> impl<A, B> Service for AndThenService<A, B>
where where
A: Service<Req>, A: Service,
B: Service<A::Response, Error = A::Error>, B: Service<Request = A::Response, Error = A::Error>,
{ {
type Request = A::Request;
type Response = B::Response; type Response = B::Response;
type Error = A::Error; type Error = A::Error;
type Future = AndThenServiceResponse<A, B, Req>; type Future = AndThenServiceResponse<A, B>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let (a, b) = &*self.0; let mut srv = self.0.borrow_mut();
let not_ready = !a.poll_ready(cx)?.is_ready(); let not_ready = !srv.0.poll_ready(cx)?.is_ready();
if !b.poll_ready(cx)?.is_ready() || not_ready { if !srv.1.poll_ready(cx)?.is_ready() || not_ready {
Poll::Pending Poll::Pending
} else { } else {
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} }
} }
fn call(&self, req: Req) -> Self::Future { fn call(&mut self, req: A::Request) -> Self::Future {
AndThenServiceResponse { AndThenServiceResponse {
state: State::A { state: State::A(self.0.borrow_mut().0.call(req), Some(self.0.clone())),
fut: self.0 .0.call(req),
b: Some(self.0.clone()),
},
} }
} }
} }
pin_project! { #[pin_project::pin_project]
pub(crate) struct AndThenServiceResponse<A, B, Req> pub(crate) struct AndThenServiceResponse<A, B>
where
A: Service<Req>,
B: Service<A::Response, Error = A::Error>,
{
#[pin]
state: State<A, B, Req>,
}
}
pin_project! {
#[project = StateProj]
enum State<A, B, Req>
where
A: Service<Req>,
B: Service<A::Response, Error = A::Error>,
{
A {
#[pin]
fut: A::Future,
b: Option<Rc<(A, B)>>,
},
B {
#[pin]
fut: B::Future,
},
}
}
impl<A, B, Req> Future for AndThenServiceResponse<A, B, Req>
where where
A: Service<Req>, A: Service,
B: Service<A::Response, Error = A::Error>, B: Service<Request = A::Response, Error = A::Error>,
{
#[pin]
state: State<A, B>,
}
#[pin_project::pin_project(project = StateProj)]
enum State<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{
A(#[pin] A::Future, Option<Rc<RefCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
impl<A, B> Future for AndThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = A::Response, Error = A::Error>,
{ {
type Output = Result<B::Response, A::Error>; type Output = Result<B::Response, A::Error>;
@@ -104,41 +88,49 @@ where
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
match this.state.as_mut().project() { match this.state.as_mut().project() {
StateProj::A { fut, b } => { StateProj::A(fut, b) => match fut.poll(cx)? {
let res = ready!(fut.poll(cx))?; Poll::Ready(res) => {
let b = b.take().unwrap(); let b = b.take().unwrap();
let fut = b.1.call(res); this.state.set(State::Empty); // drop fut A
this.state.set(State::B { fut }); let fut = b.borrow_mut().1.call(res);
self.poll(cx) this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
} }
StateProj::B { fut } => fut.poll(cx),
} }
} }
} }
/// `.and_then()` service factory combinator /// `.and_then()` service factory combinator
pub(crate) struct AndThenServiceFactory<A, B, Req> pub(crate) struct AndThenServiceFactory<A, B>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
A::Config: Clone, A::Config: Clone,
B: ServiceFactory< B: ServiceFactory<
A::Response,
Config = A::Config, Config = A::Config,
Request = A::Response,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
{ {
inner: Rc<(A, B)>, inner: Rc<(A, B)>,
_phantom: PhantomData<Req>,
} }
impl<A, B, Req> AndThenServiceFactory<A, B, Req> impl<A, B> AndThenServiceFactory<A, B>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
A::Config: Clone, A::Config: Clone,
B: ServiceFactory< B: ServiceFactory<
A::Response,
Config = A::Config, Config = A::Config,
Request = A::Response,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
@@ -147,29 +139,29 @@ where
pub(crate) fn new(a: A, b: B) -> Self { pub(crate) fn new(a: A, b: B) -> Self {
Self { Self {
inner: Rc::new((a, b)), inner: Rc::new((a, b)),
_phantom: PhantomData,
} }
} }
} }
impl<A, B, Req> ServiceFactory<Req> for AndThenServiceFactory<A, B, Req> impl<A, B> ServiceFactory for AndThenServiceFactory<A, B>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
A::Config: Clone, A::Config: Clone,
B: ServiceFactory< B: ServiceFactory<
A::Response,
Config = A::Config, Config = A::Config,
Request = A::Response,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
{ {
type Request = A::Request;
type Response = B::Response; type Response = B::Response;
type Error = A::Error; type Error = A::Error;
type Config = A::Config; type Config = A::Config;
type Service = AndThenService<A::Service, B::Service, Req>; type Service = AndThenService<A::Service, B::Service>;
type InitError = A::InitError; type InitError = A::InitError;
type Future = AndThenServiceFactoryResponse<A, B, Req>; type Future = AndThenServiceFactoryResponse<A, B>;
fn new_service(&self, cfg: A::Config) -> Self::Future { fn new_service(&self, cfg: A::Config) -> Self::Future {
let inner = &*self.inner; let inner = &*self.inner;
@@ -180,13 +172,13 @@ where
} }
} }
impl<A, B, Req> Clone for AndThenServiceFactory<A, B, Req> impl<A, B> Clone for AndThenServiceFactory<A, B>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
A::Config: Clone, A::Config: Clone,
B: ServiceFactory< B: ServiceFactory<
A::Response,
Config = A::Config, Config = A::Config,
Request = A::Response,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
@@ -194,31 +186,29 @@ where
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
inner: self.inner.clone(), inner: self.inner.clone(),
_phantom: PhantomData,
} }
} }
} }
pin_project! { #[pin_project::pin_project]
pub(crate) struct AndThenServiceFactoryResponse<A, B, Req> pub(crate) struct AndThenServiceFactoryResponse<A, B>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
B: ServiceFactory<A::Response>, B: ServiceFactory<Request = A::Response>,
{ {
#[pin] #[pin]
fut_a: A::Future, fut_a: A::Future,
#[pin] #[pin]
fut_b: B::Future, fut_b: B::Future,
a: Option<A::Service>, a: Option<A::Service>,
b: Option<B::Service>, b: Option<B::Service>,
}
} }
impl<A, B, Req> AndThenServiceFactoryResponse<A, B, Req> impl<A, B> AndThenServiceFactoryResponse<A, B>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
B: ServiceFactory<A::Response>, B: ServiceFactory<Request = A::Response>,
{ {
fn new(fut_a: A::Future, fut_b: B::Future) -> Self { fn new(fut_a: A::Future, fut_b: B::Future) -> Self {
AndThenServiceFactoryResponse { AndThenServiceFactoryResponse {
@@ -230,12 +220,12 @@ where
} }
} }
impl<A, B, Req> Future for AndThenServiceFactoryResponse<A, B, Req> impl<A, B> Future for AndThenServiceFactoryResponse<A, B>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
B: ServiceFactory<A::Response, Error = A::Error, InitError = A::InitError>, B: ServiceFactory<Request = A::Response, Error = A::Error, InitError = A::InitError>,
{ {
type Output = Result<AndThenService<A::Service, B::Service, Req>, A::InitError>; type Output = Result<AndThenService<A::Service, B::Service>, A::InitError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project(); let this = self.project();
@@ -263,31 +253,28 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use alloc::rc::Rc; use std::cell::Cell;
use core::{ use std::rc::Rc;
cell::Cell, use std::task::{Context, Poll};
task::{Context, Poll},
};
use futures_util::future::lazy; use futures_util::future::{lazy, ok, ready, Ready};
use crate::{ use crate::{fn_factory, pipeline, pipeline_factory, Service, ServiceFactory};
fn_factory, ok, pipeline, pipeline_factory, ready, Ready, Service, ServiceFactory,
};
struct Srv1(Rc<Cell<usize>>); struct Srv1(Rc<Cell<usize>>);
impl Service<&'static str> for Srv1 { impl Service for Srv1 {
type Request = &'static str;
type Response = &'static str; type Response = &'static str;
type Error = (); type Error = ();
type Future = Ready<Result<Self::Response, ()>>; type Future = Ready<Result<Self::Response, ()>>;
fn poll_ready(&self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.set(self.0.get() + 1); self.0.set(self.0.get() + 1);
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} }
fn call(&self, req: &'static str) -> Self::Future { fn call(&mut self, req: &'static str) -> Self::Future {
ok(req) ok(req)
} }
} }
@@ -295,17 +282,18 @@ mod tests {
#[derive(Clone)] #[derive(Clone)]
struct Srv2(Rc<Cell<usize>>); struct Srv2(Rc<Cell<usize>>);
impl Service<&'static str> for Srv2 { impl Service for Srv2 {
type Request = &'static str;
type Response = (&'static str, &'static str); type Response = (&'static str, &'static str);
type Error = (); type Error = ();
type Future = Ready<Result<Self::Response, ()>>; type Future = Ready<Result<Self::Response, ()>>;
fn poll_ready(&self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.set(self.0.get() + 1); self.0.set(self.0.get() + 1);
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} }
fn call(&self, req: &'static str) -> Self::Future { fn call(&mut self, req: &'static str) -> Self::Future {
ok((req, "srv2")) ok((req, "srv2"))
} }
} }
@@ -313,7 +301,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_poll_ready() { async fn test_poll_ready() {
let cnt = Rc::new(Cell::new(0)); let cnt = Rc::new(Cell::new(0));
let srv = pipeline(Srv1(cnt.clone())).and_then(Srv2(cnt.clone())); let mut srv = pipeline(Srv1(cnt.clone())).and_then(Srv2(cnt.clone()));
let res = lazy(|cx| srv.poll_ready(cx)).await; let res = lazy(|cx| srv.poll_ready(cx)).await;
assert_eq!(res, Poll::Ready(Ok(()))); assert_eq!(res, Poll::Ready(Ok(())));
assert_eq!(cnt.get(), 2); assert_eq!(cnt.get(), 2);
@@ -322,7 +310,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_call() { async fn test_call() {
let cnt = Rc::new(Cell::new(0)); let cnt = Rc::new(Cell::new(0));
let srv = pipeline(Srv1(cnt.clone())).and_then(Srv2(cnt)); let mut srv = pipeline(Srv1(cnt.clone())).and_then(Srv2(cnt));
let res = srv.call("srv1").await; let res = srv.call("srv1").await;
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), ("srv1", "srv2")); assert_eq!(res.unwrap(), ("srv1", "srv2"));
@@ -336,7 +324,7 @@ mod tests {
pipeline_factory(fn_factory(move || ready(Ok::<_, ()>(Srv1(cnt2.clone()))))) pipeline_factory(fn_factory(move || ready(Ok::<_, ()>(Srv1(cnt2.clone())))))
.and_then(move || ready(Ok(Srv2(cnt.clone())))); .and_then(move || ready(Ok(Srv2(cnt.clone()))));
let srv = new_srv.new_service(()).await.unwrap(); let mut srv = new_srv.new_service(()).await.unwrap();
let res = srv.call("srv1").await; let res = srv.call("srv1").await;
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), ("srv1", "srv2")); assert_eq!(res.unwrap(), ("srv1", "srv2"));

View File

@@ -0,0 +1,326 @@
use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use crate::{Service, ServiceFactory};
/// `Apply` service combinator
pub(crate) struct AndThenApplyFn<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
srv: Rc<RefCell<(A, B, F)>>,
r: PhantomData<(Fut, Res, Err)>,
}
impl<A, B, F, Fut, Res, Err> AndThenApplyFn<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
/// Create new `Apply` combinator
pub(crate) fn new(a: A, b: B, f: F) -> Self {
Self {
srv: Rc::new(RefCell::new((a, b, f))),
r: PhantomData,
}
}
}
impl<A, B, F, Fut, Res, Err> Clone for AndThenApplyFn<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
fn clone(&self) -> Self {
AndThenApplyFn {
srv: self.srv.clone(),
r: PhantomData,
}
}
}
impl<A, B, F, Fut, Res, Err> Service for AndThenApplyFn<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
type Request = A::Request;
type Response = Res;
type Error = Err;
type Future = AndThenApplyFnFuture<A, B, F, Fut, Res, Err>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let mut inner = self.srv.borrow_mut();
let not_ready = inner.0.poll_ready(cx)?.is_pending();
if inner.1.poll_ready(cx)?.is_pending() || not_ready {
Poll::Pending
} else {
Poll::Ready(Ok(()))
}
}
fn call(&mut self, req: A::Request) -> Self::Future {
let fut = self.srv.borrow_mut().0.call(req);
AndThenApplyFnFuture {
state: State::A(fut, Some(self.srv.clone())),
}
}
}
#[pin_project::pin_project]
pub(crate) struct AndThenApplyFnFuture<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error>,
Err: From<B::Error>,
{
#[pin]
state: State<A, B, F, Fut, Res, Err>,
}
#[pin_project::pin_project(project = StateProj)]
enum State<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error>,
Err: From<B::Error>,
{
A(#[pin] A::Future, Option<Rc<RefCell<(A, B, F)>>>),
B(#[pin] Fut),
Empty,
}
impl<A, B, F, Fut, Res, Err> Future for AndThenApplyFnFuture<A, B, F, Fut, Res, Err>
where
A: Service,
B: Service,
F: FnMut(A::Response, &mut B) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
type Output = Result<Res, Err>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project();
match this.state.as_mut().project() {
StateProj::A(fut, b) => match fut.poll(cx)? {
Poll::Ready(res) => {
let b = b.take().unwrap();
this.state.set(State::Empty);
let (_, b, f) = &mut *b.borrow_mut();
let fut = f(res, b);
this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
}
}
}
}
/// `AndThenApplyFn` service factory
pub(crate) struct AndThenApplyFnFactory<A, B, F, Fut, Res, Err> {
srv: Rc<(A, B, F)>,
r: PhantomData<(Fut, Res, Err)>,
}
impl<A, B, F, Fut, Res, Err> AndThenApplyFnFactory<A, B, F, Fut, Res, Err>
where
A: ServiceFactory,
B: ServiceFactory<Config = A::Config, InitError = A::InitError>,
F: FnMut(A::Response, &mut B::Service) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
/// Create new `ApplyNewService` new service instance
pub(crate) fn new(a: A, b: B, f: F) -> Self {
Self {
srv: Rc::new((a, b, f)),
r: PhantomData,
}
}
}
impl<A, B, F, Fut, Res, Err> Clone for AndThenApplyFnFactory<A, B, F, Fut, Res, Err> {
fn clone(&self) -> Self {
Self {
srv: self.srv.clone(),
r: PhantomData,
}
}
}
impl<A, B, F, Fut, Res, Err> ServiceFactory for AndThenApplyFnFactory<A, B, F, Fut, Res, Err>
where
A: ServiceFactory,
A::Config: Clone,
B: ServiceFactory<Config = A::Config, InitError = A::InitError>,
F: FnMut(A::Response, &mut B::Service) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
type Request = A::Request;
type Response = Res;
type Error = Err;
type Service = AndThenApplyFn<A::Service, B::Service, F, Fut, Res, Err>;
type Config = A::Config;
type InitError = A::InitError;
type Future = AndThenApplyFnFactoryResponse<A, B, F, Fut, Res, Err>;
fn new_service(&self, cfg: A::Config) -> Self::Future {
let srv = &*self.srv;
AndThenApplyFnFactoryResponse {
a: None,
b: None,
f: srv.2.clone(),
fut_a: srv.0.new_service(cfg.clone()),
fut_b: srv.1.new_service(cfg),
}
}
}
#[pin_project::pin_project]
pub(crate) struct AndThenApplyFnFactoryResponse<A, B, F, Fut, Res, Err>
where
A: ServiceFactory,
B: ServiceFactory<Config = A::Config, InitError = A::InitError>,
F: FnMut(A::Response, &mut B::Service) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error>,
Err: From<B::Error>,
{
#[pin]
fut_b: B::Future,
#[pin]
fut_a: A::Future,
f: F,
a: Option<A::Service>,
b: Option<B::Service>,
}
impl<A, B, F, Fut, Res, Err> Future for AndThenApplyFnFactoryResponse<A, B, F, Fut, Res, Err>
where
A: ServiceFactory,
B: ServiceFactory<Config = A::Config, InitError = A::InitError>,
F: FnMut(A::Response, &mut B::Service) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>,
Err: From<A::Error> + From<B::Error>,
{
type Output =
Result<AndThenApplyFn<A::Service, B::Service, F, Fut, Res, Err>, A::InitError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
if this.a.is_none() {
if let Poll::Ready(service) = this.fut_a.poll(cx)? {
*this.a = Some(service);
}
}
if this.b.is_none() {
if let Poll::Ready(service) = this.fut_b.poll(cx)? {
*this.b = Some(service);
}
}
if this.a.is_some() && this.b.is_some() {
Poll::Ready(Ok(AndThenApplyFn {
srv: Rc::new(RefCell::new((
this.a.take().unwrap(),
this.b.take().unwrap(),
this.f.clone(),
))),
r: PhantomData,
}))
} else {
Poll::Pending
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::future::{lazy, ok, Ready, TryFutureExt};
use crate::{fn_service, pipeline, pipeline_factory, Service, ServiceFactory};
#[derive(Clone)]
struct Srv;
impl Service for Srv {
type Request = ();
type Response = ();
type Error = ();
type Future = Ready<Result<(), ()>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
#[allow(clippy::unit_arg)]
fn call(&mut self, req: Self::Request) -> Self::Future {
ok(req)
}
}
#[actix_rt::test]
async fn test_service() {
let mut srv = pipeline(ok).and_then_apply_fn(Srv, |req: &'static str, s| {
s.call(()).map_ok(move |res| (req, res))
});
let res = lazy(|cx| srv.poll_ready(cx)).await;
assert_eq!(res, Poll::Ready(Ok(())));
let res = srv.call("srv").await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), ("srv", ()));
}
#[actix_rt::test]
async fn test_service_factory() {
let new_srv = pipeline_factory(|| ok::<_, ()>(fn_service(ok))).and_then_apply_fn(
|| ok(Srv),
|req: &'static str, s| s.call(()).map_ok(move |res| (req, res)),
);
let mut srv = new_srv.new_service(()).await.unwrap();
let res = lazy(|cx| srv.poll_ready(cx)).await;
assert_eq!(res, Poll::Ready(Ok(())));
let res = srv.call("srv").await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), ("srv", ()));
}
}

View File

@@ -1,239 +1,236 @@
use core::{ use std::future::Future;
future::Future, use std::marker::PhantomData;
marker::PhantomData, use std::pin::Pin;
pin::Pin, use std::task::{Context, Poll};
task::{Context, Poll},
};
use futures_core::ready;
use pin_project_lite::pin_project;
use super::{IntoService, IntoServiceFactory, Service, ServiceFactory}; use super::{IntoService, IntoServiceFactory, Service, ServiceFactory};
/// Apply transform function to a service. /// Apply transform function to a service.
/// pub fn apply_fn<T, F, R, In, Out, Err, U>(service: U, f: F) -> Apply<T, F, R, In, Out, Err>
/// The In and Out type params refer to the request and response types for the wrapped service.
pub fn apply_fn<I, S, F, Fut, Req, In, Res, Err>(
service: I,
wrap_fn: F,
) -> Apply<S, F, Req, In, Res, Err>
where where
I: IntoService<S, In>, T: Service<Error = Err>,
S: Service<In, Error = Err>, F: FnMut(In, &mut T) -> R,
F: Fn(Req, &S) -> Fut, R: Future<Output = Result<Out, Err>>,
Fut: Future<Output = Result<Res, Err>>, U: IntoService<T>,
{ {
Apply::new(service.into_service(), wrap_fn) Apply::new(service.into_service(), f)
} }
/// Service factory that produces `apply_fn` service. /// Service factory that produces `apply_fn` service.
/// pub fn apply_fn_factory<T, F, R, In, Out, Err, U>(
/// The In and Out type params refer to the request and response types for the wrapped service. service: U,
pub fn apply_fn_factory<I, SF, F, Fut, Req, In, Res, Err>(
service: I,
f: F, f: F,
) -> ApplyFactory<SF, F, Req, In, Res, Err> ) -> ApplyServiceFactory<T, F, R, In, Out, Err>
where where
I: IntoServiceFactory<SF, In>, T: ServiceFactory<Error = Err>,
SF: ServiceFactory<In, Error = Err>, F: FnMut(In, &mut T::Service) -> R + Clone,
F: Fn(Req, &SF::Service) -> Fut + Clone, R: Future<Output = Result<Out, Err>>,
Fut: Future<Output = Result<Res, Err>>, U: IntoServiceFactory<T>,
{ {
ApplyFactory::new(service.into_factory(), f) ApplyServiceFactory::new(service.into_factory(), f)
} }
/// `Apply` service combinator. /// `Apply` service combinator
/// pub struct Apply<T, F, R, In, Out, Err>
/// The In and Out type params refer to the request and response types for the wrapped service.
pub struct Apply<S, F, Req, In, Res, Err>
where where
S: Service<In, Error = Err>, T: Service<Error = Err>,
{ {
service: S, service: T,
wrap_fn: F, f: F,
_phantom: PhantomData<(Req, In, Res, Err)>, r: PhantomData<(In, Out, R)>,
} }
impl<S, F, Fut, Req, In, Res, Err> Apply<S, F, Req, In, Res, Err> impl<T, F, R, In, Out, Err> Apply<T, F, R, In, Out, Err>
where where
S: Service<In, Error = Err>, T: Service<Error = Err>,
F: Fn(Req, &S) -> Fut, F: FnMut(In, &mut T) -> R,
Fut: Future<Output = Result<Res, Err>>, R: Future<Output = Result<Out, Err>>,
{ {
/// Create new `Apply` combinator /// Create new `Apply` combinator
fn new(service: S, wrap_fn: F) -> Self { fn new(service: T, f: F) -> Self {
Self { Self {
service, service,
wrap_fn, f,
_phantom: PhantomData, r: PhantomData,
} }
} }
} }
impl<S, F, Fut, Req, In, Res, Err> Clone for Apply<S, F, Req, In, Res, Err> impl<T, F, R, In, Out, Err> Clone for Apply<T, F, R, In, Out, Err>
where where
S: Service<In, Error = Err> + Clone, T: Service<Error = Err> + Clone,
F: Fn(Req, &S) -> Fut + Clone, F: FnMut(In, &mut T) -> R + Clone,
Fut: Future<Output = Result<Res, Err>>, R: Future<Output = Result<Out, Err>>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Apply { Apply {
service: self.service.clone(), service: self.service.clone(),
wrap_fn: self.wrap_fn.clone(), f: self.f.clone(),
_phantom: PhantomData, r: PhantomData,
} }
} }
} }
impl<S, F, Fut, Req, In, Res, Err> Service<Req> for Apply<S, F, Req, In, Res, Err> impl<T, F, R, In, Out, Err> Service for Apply<T, F, R, In, Out, Err>
where where
S: Service<In, Error = Err>, T: Service<Error = Err>,
F: Fn(Req, &S) -> Fut, F: FnMut(In, &mut T) -> R,
Fut: Future<Output = Result<Res, Err>>, R: Future<Output = Result<Out, Err>>,
{ {
type Response = Res; type Request = In;
type Response = Out;
type Error = Err; type Error = Err;
type Future = Fut; type Future = R;
crate::forward_ready!(service); fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(futures_util::ready!(self.service.poll_ready(cx)))
}
fn call(&self, req: Req) -> Self::Future { fn call(&mut self, req: In) -> Self::Future {
(self.wrap_fn)(req, &self.service) (self.f)(req, &mut self.service)
} }
} }
/// `ApplyFactory` service factory combinator. /// `apply()` service factory
pub struct ApplyFactory<SF, F, Req, In, Res, Err> { pub struct ApplyServiceFactory<T, F, R, In, Out, Err>
factory: SF, where
wrap_fn: F, T: ServiceFactory<Error = Err>,
_phantom: PhantomData<(Req, In, Res, Err)>, F: FnMut(In, &mut T::Service) -> R + Clone,
R: Future<Output = Result<Out, Err>>,
{
service: T,
f: F,
r: PhantomData<(R, In, Out)>,
} }
impl<SF, F, Fut, Req, In, Res, Err> ApplyFactory<SF, F, Req, In, Res, Err> impl<T, F, R, In, Out, Err> ApplyServiceFactory<T, F, R, In, Out, Err>
where where
SF: ServiceFactory<In, Error = Err>, T: ServiceFactory<Error = Err>,
F: Fn(Req, &SF::Service) -> Fut + Clone, F: FnMut(In, &mut T::Service) -> R + Clone,
Fut: Future<Output = Result<Res, Err>>, R: Future<Output = Result<Out, Err>>,
{ {
/// Create new `ApplyFactory` new service instance /// Create new `ApplyNewService` new service instance
fn new(factory: SF, wrap_fn: F) -> Self { fn new(service: T, f: F) -> Self {
Self { Self {
factory, f,
wrap_fn, service,
_phantom: PhantomData, r: PhantomData,
} }
} }
} }
impl<SF, F, Fut, Req, In, Res, Err> Clone for ApplyFactory<SF, F, Req, In, Res, Err> impl<T, F, R, In, Out, Err> Clone for ApplyServiceFactory<T, F, R, In, Out, Err>
where where
SF: ServiceFactory<In, Error = Err> + Clone, T: ServiceFactory<Error = Err> + Clone,
F: Fn(Req, &SF::Service) -> Fut + Clone, F: FnMut(In, &mut T::Service) -> R + Clone,
Fut: Future<Output = Result<Res, Err>>, R: Future<Output = Result<Out, Err>>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
factory: self.factory.clone(), service: self.service.clone(),
wrap_fn: self.wrap_fn.clone(), f: self.f.clone(),
_phantom: PhantomData, r: PhantomData,
} }
} }
} }
impl<SF, F, Fut, Req, In, Res, Err> ServiceFactory<Req> impl<T, F, R, In, Out, Err> ServiceFactory for ApplyServiceFactory<T, F, R, In, Out, Err>
for ApplyFactory<SF, F, Req, In, Res, Err>
where where
SF: ServiceFactory<In, Error = Err>, T: ServiceFactory<Error = Err>,
F: Fn(Req, &SF::Service) -> Fut + Clone, F: FnMut(In, &mut T::Service) -> R + Clone,
Fut: Future<Output = Result<Res, Err>>, R: Future<Output = Result<Out, Err>>,
{ {
type Response = Res; type Request = In;
type Response = Out;
type Error = Err; type Error = Err;
type Config = SF::Config; type Config = T::Config;
type Service = Apply<SF::Service, F, Req, In, Res, Err>; type Service = Apply<T::Service, F, R, In, Out, Err>;
type InitError = SF::InitError; type InitError = T::InitError;
type Future = ApplyServiceFactoryResponse<SF, F, Fut, Req, In, Res, Err>; type Future = ApplyServiceFactoryResponse<T, F, R, In, Out, Err>;
fn new_service(&self, cfg: SF::Config) -> Self::Future { fn new_service(&self, cfg: T::Config) -> Self::Future {
let svc = self.factory.new_service(cfg); ApplyServiceFactoryResponse::new(self.service.new_service(cfg), self.f.clone())
ApplyServiceFactoryResponse::new(svc, self.wrap_fn.clone())
} }
} }
pin_project! { #[pin_project::pin_project]
pub struct ApplyServiceFactoryResponse<SF, F, Fut, Req, In, Res, Err> pub struct ApplyServiceFactoryResponse<T, F, R, In, Out, Err>
where
SF: ServiceFactory<In, Error = Err>,
F: Fn(Req, &SF::Service) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
{
#[pin]
fut: SF::Future,
wrap_fn: Option<F>,
_phantom: PhantomData<(Req, Res)>,
}
}
impl<SF, F, Fut, Req, In, Res, Err> ApplyServiceFactoryResponse<SF, F, Fut, Req, In, Res, Err>
where where
SF: ServiceFactory<In, Error = Err>, T: ServiceFactory<Error = Err>,
F: Fn(Req, &SF::Service) -> Fut, F: FnMut(In, &mut T::Service) -> R,
Fut: Future<Output = Result<Res, Err>>, R: Future<Output = Result<Out, Err>>,
{ {
fn new(fut: SF::Future, wrap_fn: F) -> Self { #[pin]
fut: T::Future,
f: Option<F>,
r: PhantomData<(In, Out)>,
}
impl<T, F, R, In, Out, Err> ApplyServiceFactoryResponse<T, F, R, In, Out, Err>
where
T: ServiceFactory<Error = Err>,
F: FnMut(In, &mut T::Service) -> R,
R: Future<Output = Result<Out, Err>>,
{
fn new(fut: T::Future, f: F) -> Self {
Self { Self {
f: Some(f),
fut, fut,
wrap_fn: Some(wrap_fn), r: PhantomData,
_phantom: PhantomData,
} }
} }
} }
impl<SF, F, Fut, Req, In, Res, Err> Future impl<T, F, R, In, Out, Err> Future for ApplyServiceFactoryResponse<T, F, R, In, Out, Err>
for ApplyServiceFactoryResponse<SF, F, Fut, Req, In, Res, Err>
where where
SF: ServiceFactory<In, Error = Err>, T: ServiceFactory<Error = Err>,
F: Fn(Req, &SF::Service) -> Fut, F: FnMut(In, &mut T::Service) -> R,
Fut: Future<Output = Result<Res, Err>>, R: Future<Output = Result<Out, Err>>,
{ {
type Output = Result<Apply<SF::Service, F, Req, In, Res, Err>, SF::InitError>; type Output = Result<Apply<T::Service, F, R, In, Out, Err>, T::InitError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project(); let this = self.project();
let svc = ready!(this.fut.poll(cx))?; if let Poll::Ready(svc) = this.fut.poll(cx)? {
Poll::Ready(Ok(Apply::new(svc, this.wrap_fn.take().unwrap()))) Poll::Ready(Ok(Apply::new(svc, this.f.take().unwrap())))
} else {
Poll::Pending
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use core::task::Poll; use std::task::{Context, Poll};
use futures_util::future::lazy; use futures_util::future::{lazy, ok, Ready};
use super::*; use super::*;
use crate::{ok, pipeline, pipeline_factory, Ready, Service, ServiceFactory}; use crate::{pipeline, pipeline_factory, Service, ServiceFactory};
#[derive(Clone)] #[derive(Clone)]
struct Srv; struct Srv;
impl Service<()> for Srv { impl Service for Srv {
type Request = ();
type Response = (); type Response = ();
type Error = (); type Error = ();
type Future = Ready<Result<(), ()>>; type Future = Ready<Result<(), ()>>;
crate::always_ready!(); fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&self, _: ()) -> Self::Future { fn call(&mut self, _: ()) -> Self::Future {
ok(()) ok(())
} }
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_call() { async fn test_call() {
let srv = pipeline(apply_fn(Srv, |req: &'static str, srv| { let mut srv = pipeline(apply_fn(Srv, |req: &'static str, srv| {
let fut = srv.call(()); let fut = srv.call(());
async move { async move {
fut.await.unwrap(); fut.await.unwrap();
@@ -261,7 +258,7 @@ mod tests {
}, },
)); ));
let srv = new_srv.new_service(()).await.unwrap(); let mut srv = new_srv.new_service(()).await.unwrap();
assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(()))); assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));

View File

@@ -1,233 +1,227 @@
use alloc::rc::Rc; use std::cell::RefCell;
use core::{ use std::future::Future;
future::Future, use std::marker::PhantomData;
marker::PhantomData, use std::pin::Pin;
pin::Pin, use std::rc::Rc;
task::{Context, Poll}, use std::task::{Context, Poll};
};
use futures_core::ready;
use pin_project_lite::pin_project;
use crate::{Service, ServiceFactory}; use crate::{Service, ServiceFactory};
/// Convert `Fn(Config, &Service1) -> Future<Service2>` fn to a service factory. /// Convert `Fn(Config, &mut Service1) -> Future<Service2>` fn to a service factory
pub fn apply_cfg<S1, Req, F, Cfg, Fut, S2, Err>( pub fn apply_cfg<F, C, T, R, S, E>(
srv: S1, srv: T,
f: F, f: F,
) -> impl ServiceFactory< ) -> impl ServiceFactory<
Req, Config = C,
Config = Cfg, Request = S::Request,
Response = S2::Response,
Error = S2::Error,
Service = S2,
InitError = Err,
Future = Fut,
> + Clone
where
S1: Service<Req>,
F: Fn(Cfg, &S1) -> Fut,
Fut: Future<Output = Result<S2, Err>>,
S2: Service<Req>,
{
ApplyConfigService {
srv: Rc::new((srv, f)),
_phantom: PhantomData,
}
}
/// Convert `Fn(Config, &ServiceFactory1) -> Future<ServiceFactory2>` fn to a service factory.
///
/// Service1 get constructed from `T` factory.
pub fn apply_cfg_factory<SF, Req, F, Cfg, Fut, S>(
factory: SF,
f: F,
) -> impl ServiceFactory<
Req,
Config = Cfg,
Response = S::Response, Response = S::Response,
Error = S::Error, Error = S::Error,
Service = S, Service = S,
InitError = SF::InitError, InitError = E,
Future = R,
> + Clone > + Clone
where where
SF: ServiceFactory<Req, Config = ()>, F: FnMut(C, &mut T) -> R,
F: Fn(Cfg, &SF::Service) -> Fut, T: Service,
SF::InitError: From<SF::Error>, R: Future<Output = Result<S, E>>,
Fut: Future<Output = Result<S, SF::InitError>>, S: Service,
S: Service<Req>,
{ {
ApplyConfigServiceFactory { ApplyConfigService {
srv: Rc::new((factory, f)), srv: Rc::new(RefCell::new((srv, f))),
_phantom: PhantomData, _t: PhantomData,
} }
} }
/// Convert `Fn(Config, &Server) -> Future<Service>` fn to NewService\ /// Convert `Fn(Config, &mut Service1) -> Future<Service2>` fn to a service factory
struct ApplyConfigService<S1, Req, F, Cfg, Fut, S2, Err> ///
/// Service1 get constructed from `T` factory.
pub fn apply_cfg_factory<F, C, T, R, S>(
factory: T,
f: F,
) -> impl ServiceFactory<
Config = C,
Request = S::Request,
Response = S::Response,
Error = S::Error,
Service = S,
InitError = T::InitError,
> + Clone
where where
S1: Service<Req>, F: FnMut(C, &mut T::Service) -> R,
F: Fn(Cfg, &S1) -> Fut, T: ServiceFactory<Config = ()>,
Fut: Future<Output = Result<S2, Err>>, T::InitError: From<T::Error>,
S2: Service<Req>, R: Future<Output = Result<S, T::InitError>>,
S: Service,
{ {
srv: Rc<(S1, F)>, ApplyConfigServiceFactory {
_phantom: PhantomData<(Cfg, Req, Fut, S2)>, srv: Rc::new(RefCell::new((factory, f))),
_t: PhantomData,
}
} }
impl<S1, Req, F, Cfg, Fut, S2, Err> Clone for ApplyConfigService<S1, Req, F, Cfg, Fut, S2, Err> /// Convert `Fn(Config, &mut Server) -> Future<Service>` fn to NewService\
struct ApplyConfigService<F, C, T, R, S, E>
where where
S1: Service<Req>, F: FnMut(C, &mut T) -> R,
F: Fn(Cfg, &S1) -> Fut, T: Service,
Fut: Future<Output = Result<S2, Err>>, R: Future<Output = Result<S, E>>,
S2: Service<Req>, S: Service,
{
srv: Rc<RefCell<(T, F)>>,
_t: PhantomData<(C, R, S)>,
}
impl<F, C, T, R, S, E> Clone for ApplyConfigService<F, C, T, R, S, E>
where
F: FnMut(C, &mut T) -> R,
T: Service,
R: Future<Output = Result<S, E>>,
S: Service,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
ApplyConfigService { ApplyConfigService {
srv: self.srv.clone(), srv: self.srv.clone(),
_phantom: PhantomData, _t: PhantomData,
} }
} }
} }
impl<S1, Req, F, Cfg, Fut, S2, Err> ServiceFactory<Req> impl<F, C, T, R, S, E> ServiceFactory for ApplyConfigService<F, C, T, R, S, E>
for ApplyConfigService<S1, Req, F, Cfg, Fut, S2, Err>
where where
S1: Service<Req>, F: FnMut(C, &mut T) -> R,
F: Fn(Cfg, &S1) -> Fut, T: Service,
Fut: Future<Output = Result<S2, Err>>, R: Future<Output = Result<S, E>>,
S2: Service<Req>, S: Service,
{ {
type Response = S2::Response; type Config = C;
type Error = S2::Error; type Request = S::Request;
type Config = Cfg; type Response = S::Response;
type Service = S2; type Error = S::Error;
type Service = S;
type InitError = Err; type InitError = E;
type Future = Fut; type Future = R;
fn new_service(&self, cfg: Cfg) -> Self::Future { fn new_service(&self, cfg: C) -> Self::Future {
let (t, f) = &*self.srv; let (t, f) = &mut *self.srv.borrow_mut();
f(cfg, t) f(cfg, t)
} }
} }
/// Convert `Fn(&Config) -> Future<Service>` fn to NewService /// Convert `Fn(&Config) -> Future<Service>` fn to NewService
struct ApplyConfigServiceFactory<SF, Req, F, Cfg, Fut, S> struct ApplyConfigServiceFactory<F, C, T, R, S>
where where
SF: ServiceFactory<Req, Config = ()>, F: FnMut(C, &mut T::Service) -> R,
F: Fn(Cfg, &SF::Service) -> Fut, T: ServiceFactory<Config = ()>,
Fut: Future<Output = Result<S, SF::InitError>>, R: Future<Output = Result<S, T::InitError>>,
S: Service<Req>, S: Service,
{ {
srv: Rc<(SF, F)>, srv: Rc<RefCell<(T, F)>>,
_phantom: PhantomData<(Cfg, Req, Fut, S)>, _t: PhantomData<(C, R, S)>,
} }
impl<SF, Req, F, Cfg, Fut, S> Clone for ApplyConfigServiceFactory<SF, Req, F, Cfg, Fut, S> impl<F, C, T, R, S> Clone for ApplyConfigServiceFactory<F, C, T, R, S>
where where
SF: ServiceFactory<Req, Config = ()>, F: FnMut(C, &mut T::Service) -> R,
F: Fn(Cfg, &SF::Service) -> Fut, T: ServiceFactory<Config = ()>,
Fut: Future<Output = Result<S, SF::InitError>>, R: Future<Output = Result<S, T::InitError>>,
S: Service<Req>, S: Service,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
srv: self.srv.clone(), srv: self.srv.clone(),
_phantom: PhantomData, _t: PhantomData,
} }
} }
} }
impl<SF, Req, F, Cfg, Fut, S> ServiceFactory<Req> impl<F, C, T, R, S> ServiceFactory for ApplyConfigServiceFactory<F, C, T, R, S>
for ApplyConfigServiceFactory<SF, Req, F, Cfg, Fut, S>
where where
SF: ServiceFactory<Req, Config = ()>, F: FnMut(C, &mut T::Service) -> R,
SF::InitError: From<SF::Error>, T: ServiceFactory<Config = ()>,
F: Fn(Cfg, &SF::Service) -> Fut, T::InitError: From<T::Error>,
Fut: Future<Output = Result<S, SF::InitError>>, R: Future<Output = Result<S, T::InitError>>,
S: Service<Req>, S: Service,
{ {
type Config = C;
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Config = Cfg;
type Service = S; type Service = S;
type InitError = SF::InitError; type InitError = T::InitError;
type Future = ApplyConfigServiceFactoryResponse<SF, Req, F, Cfg, Fut, S>; type Future = ApplyConfigServiceFactoryResponse<F, C, T, R, S>;
fn new_service(&self, cfg: Cfg) -> Self::Future { fn new_service(&self, cfg: C) -> Self::Future {
ApplyConfigServiceFactoryResponse { ApplyConfigServiceFactoryResponse {
cfg: Some(cfg), cfg: Some(cfg),
store: self.srv.clone(), store: self.srv.clone(),
state: State::A { state: State::A(self.srv.borrow().0.new_service(())),
fut: self.srv.0.new_service(()),
},
} }
} }
} }
pin_project! { #[pin_project::pin_project]
struct ApplyConfigServiceFactoryResponse<SF, Req, F, Cfg, Fut, S> struct ApplyConfigServiceFactoryResponse<F, C, T, R, S>
where
SF: ServiceFactory<Req, Config = ()>,
SF::InitError: From<SF::Error>,
F: Fn(Cfg, &SF::Service) -> Fut,
Fut: Future<Output = Result<S, SF::InitError>>,
S: Service<Req>,
{
cfg: Option<Cfg>,
store: Rc<(SF, F)>,
#[pin]
state: State<SF, Fut, S, Req>,
}
}
pin_project! {
#[project = StateProj]
enum State<SF, Fut, S, Req>
where
SF: ServiceFactory<Req, Config = ()>,
SF::InitError: From<SF::Error>,
Fut: Future<Output = Result<S, SF::InitError>>,
S: Service<Req>,
{
A { #[pin] fut: SF::Future },
B { svc: SF::Service },
C { #[pin] fut: Fut },
}
}
impl<SF, Req, F, Cfg, Fut, S> Future
for ApplyConfigServiceFactoryResponse<SF, Req, F, Cfg, Fut, S>
where where
SF: ServiceFactory<Req, Config = ()>, F: FnMut(C, &mut T::Service) -> R,
SF::InitError: From<SF::Error>, T: ServiceFactory<Config = ()>,
F: Fn(Cfg, &SF::Service) -> Fut, T::InitError: From<T::Error>,
Fut: Future<Output = Result<S, SF::InitError>>, R: Future<Output = Result<S, T::InitError>>,
S: Service<Req>, S: Service,
{ {
type Output = Result<S, SF::InitError>; cfg: Option<C>,
store: Rc<RefCell<(T, F)>>,
#[pin]
state: State<T, R, S>,
}
#[pin_project::pin_project(project = StateProj)]
enum State<T, R, S>
where
T: ServiceFactory<Config = ()>,
T::InitError: From<T::Error>,
R: Future<Output = Result<S, T::InitError>>,
S: Service,
{
A(#[pin] T::Future),
B(T::Service),
C(#[pin] R),
}
impl<F, C, T, R, S> Future for ApplyConfigServiceFactoryResponse<F, C, T, R, S>
where
F: FnMut(C, &mut T::Service) -> R,
T: ServiceFactory<Config = ()>,
T::InitError: From<T::Error>,
R: Future<Output = Result<S, T::InitError>>,
S: Service,
{
type Output = Result<S, T::InitError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
match this.state.as_mut().project() { match this.state.as_mut().project() {
StateProj::A { fut } => { StateProj::A(fut) => match fut.poll(cx)? {
let svc = ready!(fut.poll(cx))?; Poll::Pending => Poll::Pending,
this.state.set(State::B { svc }); Poll::Ready(srv) => {
self.poll(cx) this.state.set(State::B(srv));
} self.poll(cx)
StateProj::B { svc } => {
ready!(svc.poll_ready(cx))?;
{
let (_, f) = &**this.store;
let fut = f(this.cfg.take().unwrap(), svc);
this.state.set(State::C { fut });
} }
self.poll(cx) },
} StateProj::B(srv) => match srv.poll_ready(cx)? {
StateProj::C { fut } => fut.poll(cx), Poll::Ready(_) => {
{
let (_, f) = &mut *this.store.borrow_mut();
let fut = f(this.cfg.take().unwrap(), srv);
this.state.set(State::C(fut));
}
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateProj::C(fut) => fut.poll(cx),
} }
} }
} }

View File

@@ -1,135 +1,145 @@
//! Trait object forms of services and service factories. use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use alloc::{boxed::Box, rc::Rc}; use futures_util::future::FutureExt;
use core::{future::Future, pin::Pin};
use crate::{Service, ServiceFactory}; use crate::{Service, ServiceFactory};
/// A boxed future without a Send bound or lifetime parameters. pub type BoxFuture<I, E> = Pin<Box<dyn Future<Output = Result<I, E>>>>;
pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;
macro_rules! service_object { pub type BoxService<Req, Res, Err> =
($name: ident, $type: tt, $fn_name: ident) => { Box<dyn Service<Request = Req, Response = Res, Error = Err, Future = BoxFuture<Res, Err>>>;
/// Type alias for service trait object.
pub type $name<Req, Res, Err> = $type<
dyn Service<Req, Response = Res, Error = Err, Future = BoxFuture<Result<Res, Err>>>,
>;
/// Create service trait object. pub struct BoxServiceFactory<C, Req, Res, Err, InitErr>(Inner<C, Req, Res, Err, InitErr>);
pub fn $fn_name<S, Req>(service: S) -> $name<Req, S::Response, S::Error>
where
S: Service<Req> + 'static,
Req: 'static,
S::Future: 'static,
{
$type::new(ServiceWrapper::new(service))
}
};
}
service_object!(BoxService, Box, service); /// Create boxed service factory
service_object!(RcService, Rc, rc_service); pub fn factory<T>(
factory: T,
struct ServiceWrapper<S> { ) -> BoxServiceFactory<T::Config, T::Request, T::Response, T::Error, T::InitError>
inner: S,
}
impl<S> ServiceWrapper<S> {
fn new(inner: S) -> Self {
Self { inner }
}
}
impl<S, Req, Res, Err> Service<Req> for ServiceWrapper<S>
where where
S: Service<Req, Response = Res, Error = Err>, T: ServiceFactory + 'static,
S::Future: 'static, T::Request: 'static,
T::Response: 'static,
T::Service: 'static,
T::Future: 'static,
T::Error: 'static,
T::InitError: 'static,
{ {
type Response = Res; BoxServiceFactory(Box::new(FactoryWrapper {
type Error = Err; factory,
type Future = BoxFuture<Result<Res, Err>>; _t: std::marker::PhantomData,
}))
crate::forward_ready!(inner);
fn call(&self, req: Req) -> Self::Future {
Box::pin(self.inner.call(req))
}
} }
/// Wrapper for a service factory trait object that will produce a boxed trait object service. /// Create boxed service
pub struct BoxServiceFactory<Cfg, Req, Res, Err, InitErr>(Inner<Cfg, Req, Res, Err, InitErr>); pub fn service<T>(service: T) -> BoxService<T::Request, T::Response, T::Error>
/// Create service factory trait object.
pub fn factory<SF, Req>(
factory: SF,
) -> BoxServiceFactory<SF::Config, Req, SF::Response, SF::Error, SF::InitError>
where where
SF: ServiceFactory<Req> + 'static, T: Service + 'static,
Req: 'static, T::Future: 'static,
SF::Response: 'static,
SF::Service: 'static,
SF::Future: 'static,
SF::Error: 'static,
SF::InitError: 'static,
{ {
BoxServiceFactory(Box::new(FactoryWrapper(factory))) Box::new(ServiceWrapper(service))
} }
type Inner<C, Req, Res, Err, InitErr> = Box< type Inner<C, Req, Res, Err, InitErr> = Box<
dyn ServiceFactory< dyn ServiceFactory<
Req,
Config = C, Config = C,
Request = Req,
Response = Res, Response = Res,
Error = Err, Error = Err,
InitError = InitErr, InitError = InitErr,
Service = BoxService<Req, Res, Err>, Service = BoxService<Req, Res, Err>,
Future = BoxFuture<Result<BoxService<Req, Res, Err>, InitErr>>, Future = BoxFuture<BoxService<Req, Res, Err>, InitErr>,
>, >,
>; >;
impl<C, Req, Res, Err, InitErr> ServiceFactory<Req> impl<C, Req, Res, Err, InitErr> ServiceFactory for BoxServiceFactory<C, Req, Res, Err, InitErr>
for BoxServiceFactory<C, Req, Res, Err, InitErr>
where where
Req: 'static, Req: 'static,
Res: 'static, Res: 'static,
Err: 'static, Err: 'static,
InitErr: 'static, InitErr: 'static,
{ {
type Request = Req;
type Response = Res; type Response = Res;
type Error = Err; type Error = Err;
type InitError = InitErr;
type Config = C; type Config = C;
type Service = BoxService<Req, Res, Err>; type Service = BoxService<Req, Res, Err>;
type InitError = InitErr;
type Future = BoxFuture<Result<Self::Service, InitErr>>; type Future = BoxFuture<Self::Service, InitErr>;
fn new_service(&self, cfg: C) -> Self::Future { fn new_service(&self, cfg: C) -> Self::Future {
self.0.new_service(cfg) self.0.new_service(cfg)
} }
} }
struct FactoryWrapper<SF>(SF); struct FactoryWrapper<C, T: ServiceFactory> {
factory: T,
_t: std::marker::PhantomData<C>,
}
impl<SF, Req, Cfg, Res, Err, InitErr> ServiceFactory<Req> for FactoryWrapper<SF> impl<C, T, Req, Res, Err, InitErr> ServiceFactory for FactoryWrapper<C, T>
where where
Req: 'static, Req: 'static,
Res: 'static, Res: 'static,
Err: 'static, Err: 'static,
InitErr: 'static, InitErr: 'static,
SF: ServiceFactory<Req, Config = Cfg, Response = Res, Error = Err, InitError = InitErr>, T: ServiceFactory<
SF::Future: 'static, Config = C,
SF::Service: 'static, Request = Req,
<SF::Service as Service<Req>>::Future: 'static, Response = Res,
Error = Err,
InitError = InitErr,
>,
T::Future: 'static,
T::Service: 'static,
<T::Service as Service>::Future: 'static,
{ {
type Request = Req;
type Response = Res; type Response = Res;
type Error = Err; type Error = Err;
type Config = Cfg;
type Service = BoxService<Req, Res, Err>;
type InitError = InitErr; type InitError = InitErr;
type Future = BoxFuture<Result<Self::Service, Self::InitError>>; type Config = C;
type Service = BoxService<Req, Res, Err>;
type Future = BoxFuture<Self::Service, Self::InitError>;
fn new_service(&self, cfg: Cfg) -> Self::Future { fn new_service(&self, cfg: C) -> Self::Future {
let f = self.0.new_service(cfg); Box::pin(
Box::pin(async { f.await.map(|s| Box::new(ServiceWrapper::new(s)) as _) }) self.factory
.new_service(cfg)
.map(|res| res.map(ServiceWrapper::boxed)),
)
}
}
struct ServiceWrapper<T: Service>(T);
impl<T> ServiceWrapper<T>
where
T: Service + 'static,
T::Future: 'static,
{
fn boxed(service: T) -> BoxService<T::Request, T::Response, T::Error> {
Box::new(ServiceWrapper(service))
}
}
impl<T, Req, Res, Err> Service for ServiceWrapper<T>
where
T: Service<Request = Req, Response = Res, Error = Err>,
T::Future: 'static,
{
type Request = Req;
type Response = Res;
type Error = Err;
type Future = BoxFuture<Res, Err>;
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(ctx)
}
fn call(&mut self, req: Self::Request) -> Self::Future {
Box::pin(self.0.call(req))
} }
} }

View File

@@ -1,86 +0,0 @@
use crate::{
map::Map, map_err::MapErr, transform_err::TransformMapInitErr, Service, ServiceFactory,
Transform,
};
pub trait ServiceExt<Req>: Service<Req> {
/// Map this service's output to a different type, returning a new service
/// of the resulting type.
///
/// This function is similar to the `Option::map` or `Iterator::map` where
/// it will change the type of the underlying service.
///
/// Note that this function consumes the receiving service and returns a
/// wrapped version of it, similar to the existing `map` methods in the
/// standard library.
fn map<F, R>(self, f: F) -> Map<Self, F, Req, R>
where
Self: Sized,
F: FnMut(Self::Response) -> R,
{
Map::new(self, f)
}
/// Map this service's error to a different error, returning a new service.
///
/// This function is similar to the `Result::map_err` where it will change
/// the error type of the underlying service. For example, this can be useful to
/// ensure that services have the same error type.
///
/// Note that this function consumes the receiving service and returns a
/// wrapped version of it.
fn map_err<F, E>(self, f: F) -> MapErr<Self, Req, F, E>
where
Self: Sized,
F: Fn(Self::Error) -> E,
{
MapErr::new(self, f)
}
}
impl<S, Req> ServiceExt<Req> for S where S: Service<Req> {}
pub trait ServiceFactoryExt<Req>: ServiceFactory<Req> {
/// Map this service's output to a different type, returning a new service
/// of the resulting type.
fn map<F, R>(self, f: F) -> crate::map::MapServiceFactory<Self, F, Req, R>
where
Self: Sized,
F: FnMut(Self::Response) -> R + Clone,
{
crate::map::MapServiceFactory::new(self, f)
}
/// Map this service's error to a different error, returning a new service.
fn map_err<F, E>(self, f: F) -> crate::map_err::MapErrServiceFactory<Self, Req, F, E>
where
Self: Sized,
F: Fn(Self::Error) -> E + Clone,
{
crate::map_err::MapErrServiceFactory::new(self, f)
}
/// Map this factory's init error to a different error, returning a new service.
fn map_init_err<F, E>(self, f: F) -> crate::map_init_err::MapInitErr<Self, F, Req, E>
where
Self: Sized,
F: Fn(Self::InitError) -> E + Clone,
{
crate::map_init_err::MapInitErr::new(self, f)
}
}
impl<SF, Req> ServiceFactoryExt<Req> for SF where SF: ServiceFactory<Req> {}
pub trait TransformExt<S, Req>: Transform<S, Req> {
/// Return a new `Transform` whose init error is mapped to to a different type.
fn map_init_err<F, E>(self, f: F) -> TransformMapInitErr<Self, S, Req, F, E>
where
Self: Sized,
F: Fn(Self::InitError) -> E + Clone,
{
TransformMapInitErr::new(self, f)
}
}
impl<T, Req> TransformExt<T, Req> for T where T: Transform<T, Req> {}

View File

@@ -1,13 +1,17 @@
use core::{future::Future, marker::PhantomData}; use std::future::Future;
use std::marker::PhantomData;
use std::task::{Context, Poll};
use crate::{ok, IntoService, IntoServiceFactory, Ready, Service, ServiceFactory}; use futures_util::future::{ok, Ready};
use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory};
/// Create `ServiceFactory` for function that can act as a `Service` /// Create `ServiceFactory` for function that can act as a `Service`
pub fn fn_service<F, Fut, Req, Res, Err, Cfg>( pub fn fn_service<F, Fut, Req, Res, Err, Cfg>(
f: F, f: F,
) -> FnServiceFactory<F, Fut, Req, Res, Err, Cfg> ) -> FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
where where
F: Fn(Req) -> Fut + Clone, F: FnMut(Req) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
FnServiceFactory::new(f) FnServiceFactory::new(f)
@@ -15,8 +19,9 @@ where
/// Create `ServiceFactory` for function that can produce services /// Create `ServiceFactory` for function that can produce services
/// ///
/// # Examples /// # Example
/// ``` ///
/// ```rust
/// use std::io; /// use std::io;
/// use actix_service::{fn_factory, fn_service, Service, ServiceFactory}; /// use actix_service::{fn_factory, fn_service, Service, ServiceFactory};
/// use futures_util::future::ok; /// use futures_util::future::ok;
@@ -38,7 +43,7 @@ where
/// }); /// });
/// ///
/// // construct new service /// // construct new service
/// let srv = factory.new_service(()).await?; /// let mut srv = factory.new_service(()).await?;
/// ///
/// // now we can use `div` service /// // now we can use `div` service
/// let result = srv.call((10, 20)).await?; /// let result = srv.call((10, 20)).await?;
@@ -48,11 +53,9 @@ where
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
pub fn fn_factory<F, Cfg, Srv, Req, Fut, Err>( pub fn fn_factory<F, Cfg, Srv, Fut, Err>(f: F) -> FnServiceNoConfig<F, Cfg, Srv, Fut, Err>
f: F,
) -> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
where where
Srv: Service<Req>, Srv: Service,
F: Fn() -> Fut, F: Fn() -> Fut,
Fut: Future<Output = Result<Srv, Err>>, Fut: Future<Output = Result<Srv, Err>>,
{ {
@@ -61,11 +64,12 @@ where
/// Create `ServiceFactory` for function that accepts config argument and can produce services /// Create `ServiceFactory` for function that accepts config argument and can produce services
/// ///
/// Any function that has following form `Fn(Config) -> Future<Output = Service>` could act as /// Any function that has following form `Fn(Config) -> Future<Output = Service>` could
/// a `ServiceFactory`. /// act as a `ServiceFactory`.
/// ///
/// # Examples /// # Example
/// ``` ///
/// ```rust
/// use std::io; /// use std::io;
/// use actix_service::{fn_factory_with_config, fn_service, Service, ServiceFactory}; /// use actix_service::{fn_factory_with_config, fn_service, Service, ServiceFactory};
/// use futures_util::future::ok; /// use futures_util::future::ok;
@@ -79,7 +83,7 @@ where
/// }); /// });
/// ///
/// // construct new service with config argument /// // construct new service with config argument
/// let srv = factory.new_service(10).await?; /// let mut srv = factory.new_service(10).await?;
/// ///
/// let result = srv.call(10).await?; /// let result = srv.call(10).await?;
/// assert_eq!(result, 100); /// assert_eq!(result, 100);
@@ -88,13 +92,13 @@ where
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
pub fn fn_factory_with_config<F, Fut, Cfg, Srv, Req, Err>( pub fn fn_factory_with_config<F, Fut, Cfg, Srv, Err>(
f: F, f: F,
) -> FnServiceConfig<F, Fut, Cfg, Srv, Req, Err> ) -> FnServiceConfig<F, Fut, Cfg, Srv, Err>
where where
F: Fn(Cfg) -> Fut, F: Fn(Cfg) -> Fut,
Fut: Future<Output = Result<Srv, Err>>, Fut: Future<Output = Result<Srv, Err>>,
Srv: Service<Req>, Srv: Service,
{ {
FnServiceConfig::new(f) FnServiceConfig::new(f)
} }
@@ -128,25 +132,28 @@ where
} }
} }
impl<F, Fut, Req, Res, Err> Service<Req> for FnService<F, Fut, Req, Res, Err> impl<F, Fut, Req, Res, Err> Service for FnService<F, Fut, Req, Res, Err>
where where
F: Fn(Req) -> Fut, F: FnMut(Req) -> Fut,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
type Request = Req;
type Response = Res; type Response = Res;
type Error = Err; type Error = Err;
type Future = Fut; type Future = Fut;
crate::always_ready!(); fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&self, req: Req) -> Self::Future { fn call(&mut self, req: Req) -> Self::Future {
(self.f)(req) (self.f)(req)
} }
} }
impl<F, Fut, Req, Res, Err> IntoService<FnService<F, Fut, Req, Res, Err>, Req> for F impl<F, Fut, Req, Res, Err> IntoService<FnService<F, Fut, Req, Res, Err>> for F
where where
F: Fn(Req) -> Fut, F: FnMut(Req) -> Fut,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
fn into_service(self) -> FnService<F, Fut, Req, Res, Err> { fn into_service(self) -> FnService<F, Fut, Req, Res, Err> {
@@ -156,7 +163,7 @@ where
pub struct FnServiceFactory<F, Fut, Req, Res, Err, Cfg> pub struct FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
where where
F: Fn(Req) -> Fut, F: FnMut(Req) -> Fut,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
f: F, f: F,
@@ -165,7 +172,7 @@ where
impl<F, Fut, Req, Res, Err, Cfg> FnServiceFactory<F, Fut, Req, Res, Err, Cfg> impl<F, Fut, Req, Res, Err, Cfg> FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
where where
F: Fn(Req) -> Fut + Clone, F: FnMut(Req) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
fn new(f: F) -> Self { fn new(f: F) -> Self {
@@ -175,7 +182,7 @@ where
impl<F, Fut, Req, Res, Err, Cfg> Clone for FnServiceFactory<F, Fut, Req, Res, Err, Cfg> impl<F, Fut, Req, Res, Err, Cfg> Clone for FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
where where
F: Fn(Req) -> Fut + Clone, F: FnMut(Req) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
@@ -183,28 +190,31 @@ where
} }
} }
impl<F, Fut, Req, Res, Err> Service<Req> for FnServiceFactory<F, Fut, Req, Res, Err, ()> impl<F, Fut, Req, Res, Err> Service for FnServiceFactory<F, Fut, Req, Res, Err, ()>
where where
F: Fn(Req) -> Fut + Clone, F: FnMut(Req) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
type Request = Req;
type Response = Res; type Response = Res;
type Error = Err; type Error = Err;
type Future = Fut; type Future = Fut;
crate::always_ready!(); fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&self, req: Req) -> Self::Future { fn call(&mut self, req: Self::Request) -> Self::Future {
(self.f)(req) (self.f)(req)
} }
} }
impl<F, Fut, Req, Res, Err, Cfg> ServiceFactory<Req> impl<F, Fut, Req, Res, Err, Cfg> ServiceFactory for FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
for FnServiceFactory<F, Fut, Req, Res, Err, Cfg>
where where
F: Fn(Req) -> Fut + Clone, F: FnMut(Req) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
{ {
type Request = Req;
type Response = Res; type Response = Res;
type Error = Err; type Error = Err;
@@ -219,7 +229,7 @@ where
} }
impl<F, Fut, Req, Res, Err, Cfg> impl<F, Fut, Req, Res, Err, Cfg>
IntoServiceFactory<FnServiceFactory<F, Fut, Req, Res, Err, Cfg>, Req> for F IntoServiceFactory<FnServiceFactory<F, Fut, Req, Res, Err, Cfg>> for F
where where
F: Fn(Req) -> Fut + Clone, F: Fn(Req) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>, Fut: Future<Output = Result<Res, Err>>,
@@ -230,32 +240,32 @@ where
} }
/// Convert `Fn(&Config) -> Future<Service>` fn to NewService /// Convert `Fn(&Config) -> Future<Service>` fn to NewService
pub struct FnServiceConfig<F, Fut, Cfg, Srv, Req, Err> pub struct FnServiceConfig<F, Fut, Cfg, Srv, Err>
where where
F: Fn(Cfg) -> Fut, F: Fn(Cfg) -> Fut,
Fut: Future<Output = Result<Srv, Err>>, Fut: Future<Output = Result<Srv, Err>>,
Srv: Service<Req>, Srv: Service,
{ {
f: F, f: F,
_t: PhantomData<(Fut, Cfg, Req, Srv, Err)>, _t: PhantomData<(Fut, Cfg, Srv, Err)>,
} }
impl<F, Fut, Cfg, Srv, Req, Err> FnServiceConfig<F, Fut, Cfg, Srv, Req, Err> impl<F, Fut, Cfg, Srv, Err> FnServiceConfig<F, Fut, Cfg, Srv, Err>
where where
F: Fn(Cfg) -> Fut, F: Fn(Cfg) -> Fut,
Fut: Future<Output = Result<Srv, Err>>, Fut: Future<Output = Result<Srv, Err>>,
Srv: Service<Req>, Srv: Service,
{ {
fn new(f: F) -> Self { fn new(f: F) -> Self {
FnServiceConfig { f, _t: PhantomData } FnServiceConfig { f, _t: PhantomData }
} }
} }
impl<F, Fut, Cfg, Srv, Req, Err> Clone for FnServiceConfig<F, Fut, Cfg, Srv, Req, Err> impl<F, Fut, Cfg, Srv, Err> Clone for FnServiceConfig<F, Fut, Cfg, Srv, Err>
where where
F: Fn(Cfg) -> Fut + Clone, F: Fn(Cfg) -> Fut + Clone,
Fut: Future<Output = Result<Srv, Err>>, Fut: Future<Output = Result<Srv, Err>>,
Srv: Service<Req>, Srv: Service,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
FnServiceConfig { FnServiceConfig {
@@ -265,13 +275,13 @@ where
} }
} }
impl<F, Fut, Cfg, Srv, Req, Err> ServiceFactory<Req> impl<F, Fut, Cfg, Srv, Err> ServiceFactory for FnServiceConfig<F, Fut, Cfg, Srv, Err>
for FnServiceConfig<F, Fut, Cfg, Srv, Req, Err>
where where
F: Fn(Cfg) -> Fut, F: Fn(Cfg) -> Fut,
Fut: Future<Output = Result<Srv, Err>>, Fut: Future<Output = Result<Srv, Err>>,
Srv: Service<Req>, Srv: Service,
{ {
type Request = Srv::Request;
type Response = Srv::Response; type Response = Srv::Response;
type Error = Srv::Error; type Error = Srv::Error;
@@ -286,83 +296,82 @@ where
} }
/// Converter for `Fn() -> Future<Service>` fn /// Converter for `Fn() -> Future<Service>` fn
pub struct FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err> pub struct FnServiceNoConfig<F, C, S, R, E>
where where
F: Fn() -> Fut, F: Fn() -> R,
Srv: Service<Req>, S: Service,
Fut: Future<Output = Result<Srv, Err>>, R: Future<Output = Result<S, E>>,
{ {
f: F, f: F,
_t: PhantomData<(Cfg, Req)>, _t: PhantomData<C>,
} }
impl<F, Cfg, Srv, Req, Fut, Err> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err> impl<F, C, S, R, E> FnServiceNoConfig<F, C, S, R, E>
where where
F: Fn() -> Fut, F: Fn() -> R,
Fut: Future<Output = Result<Srv, Err>>, R: Future<Output = Result<S, E>>,
Srv: Service<Req>, S: Service,
{ {
fn new(f: F) -> Self { fn new(f: F) -> Self {
Self { f, _t: PhantomData } Self { f, _t: PhantomData }
} }
} }
impl<F, Cfg, Srv, Req, Fut, Err> ServiceFactory<Req> impl<F, C, S, R, E> ServiceFactory for FnServiceNoConfig<F, C, S, R, E>
for FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>
where where
F: Fn() -> Fut, F: Fn() -> R,
Fut: Future<Output = Result<Srv, Err>>, R: Future<Output = Result<S, E>>,
Srv: Service<Req>, S: Service,
{ {
type Response = Srv::Response; type Request = S::Request;
type Error = Srv::Error; type Response = S::Response;
type Config = Cfg; type Error = S::Error;
type Service = Srv; type Service = S;
type InitError = Err; type Config = C;
type Future = Fut; type InitError = E;
type Future = R;
fn new_service(&self, _: Cfg) -> Self::Future { fn new_service(&self, _: C) -> Self::Future {
(self.f)() (self.f)()
} }
} }
impl<F, Cfg, Srv, Req, Fut, Err> Clone for FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err> impl<F, C, S, R, E> Clone for FnServiceNoConfig<F, C, S, R, E>
where where
F: Fn() -> Fut + Clone, F: Fn() -> R + Clone,
Fut: Future<Output = Result<Srv, Err>>, R: Future<Output = Result<S, E>>,
Srv: Service<Req>, S: Service,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self::new(self.f.clone()) Self::new(self.f.clone())
} }
} }
impl<F, Cfg, Srv, Req, Fut, Err> impl<F, C, S, R, E> IntoServiceFactory<FnServiceNoConfig<F, C, S, R, E>> for F
IntoServiceFactory<FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err>, Req> for F
where where
F: Fn() -> Fut, F: Fn() -> R,
Fut: Future<Output = Result<Srv, Err>>, R: Future<Output = Result<S, E>>,
Srv: Service<Req>, S: Service,
{ {
fn into_factory(self) -> FnServiceNoConfig<F, Cfg, Srv, Req, Fut, Err> { fn into_factory(self) -> FnServiceNoConfig<F, C, S, R, E> {
FnServiceNoConfig::new(self) FnServiceNoConfig::new(self)
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use core::task::Poll; use std::task::Poll;
use futures_util::future::lazy; use futures_util::future::{lazy, ok};
use super::*; use super::*;
use crate::{ok, Service, ServiceFactory}; use crate::{Service, ServiceFactory};
#[actix_rt::test] #[actix_rt::test]
async fn test_fn_service() { async fn test_fn_service() {
let new_srv = fn_service(|()| ok::<_, ()>("srv")); let new_srv = fn_service(|()| ok::<_, ()>("srv"));
let srv = new_srv.new_service(()).await.unwrap(); let mut srv = new_srv.new_service(()).await.unwrap();
let res = srv.call(()).await; let res = srv.call(()).await;
assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(()))); assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
assert!(res.is_ok()); assert!(res.is_ok());
@@ -371,7 +380,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_fn_service_service() { async fn test_fn_service_service() {
let srv = fn_service(|()| ok::<_, ()>("srv")); let mut srv = fn_service(|()| ok::<_, ()>("srv"));
let res = srv.call(()).await; let res = srv.call(()).await;
assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(()))); assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
@@ -385,7 +394,7 @@ mod tests {
ok::<_, ()>(fn_service(move |()| ok::<_, ()>(("srv", cfg)))) ok::<_, ()>(fn_service(move |()| ok::<_, ()>(("srv", cfg))))
}); });
let srv = new_srv.new_service(1).await.unwrap(); let mut srv = new_srv.new_service(1).await.unwrap();
let res = srv.call(()).await; let res = srv.call(()).await;
assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(()))); assert_eq!(lazy(|cx| srv.poll_ready(cx)).await, Poll::Ready(Ok(())));
assert!(res.is_ok()); assert!(res.is_ok());

View File

@@ -1,47 +1,37 @@
//! See [`Service`] docs for information on this crate's foundational trait. //! See [`Service`] docs for information on this crate's foundational trait.
#![no_std]
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
extern crate alloc; use std::cell::RefCell;
use std::future::Future;
use alloc::{boxed::Box, rc::Rc, sync::Arc}; use std::rc::Rc;
use core::{ use std::sync::Arc;
cell::RefCell, use std::task::{self, Context, Poll};
future::Future,
task::{self, Context, Poll},
};
mod and_then; mod and_then;
mod and_then_apply_fn;
mod apply; mod apply;
mod apply_cfg; mod apply_cfg;
pub mod boxed; pub mod boxed;
mod ext;
mod fn_service; mod fn_service;
mod macros;
mod map; mod map;
mod map_config; mod map_config;
mod map_err; mod map_err;
mod map_init_err; mod map_init_err;
mod pipeline; mod pipeline;
mod ready;
mod then; mod then;
mod transform; mod transform;
mod transform_err; mod transform_err;
pub use self::apply::{apply_fn, apply_fn_factory}; pub use self::apply::{apply_fn, apply_fn_factory};
pub use self::apply_cfg::{apply_cfg, apply_cfg_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::fn_service::{fn_factory, fn_factory_with_config, fn_service};
pub use self::map_config::{map_config, unit_config}; pub use self::map_config::{map_config, unit_config};
pub use self::pipeline::{pipeline, pipeline_factory, Pipeline, PipelineFactory}; pub use self::pipeline::{pipeline, pipeline_factory, Pipeline, PipelineFactory};
pub use self::transform::{apply, ApplyTransform, Transform}; pub use self::transform::{apply, Transform};
#[allow(unused_imports)]
use self::ready::{err, ok, ready, Ready};
/// An asynchronous operation from `Request` to a `Response`. /// An asynchronous operation from `Request` to a `Response`.
/// ///
@@ -49,7 +39,7 @@ use self::ready::{err, ok, ready, Ready};
/// replies. You can think about a service as a function with one argument that returns some result /// replies. You can think about a service as a function with one argument that returns some result
/// asynchronously. Conceptually, the operation looks like this: /// asynchronously. Conceptually, the operation looks like this:
/// ///
/// ```ignore /// ```rust,ignore
/// async fn(Request) -> Result<Response, Err> /// async fn(Request) -> Result<Response, Err>
/// ``` /// ```
/// ///
@@ -61,7 +51,7 @@ use self::ready::{err, ok, ready, Ready};
/// simple API surfaces. This leads to simpler design of each service, improves test-ability and /// simple API surfaces. This leads to simpler design of each service, improves test-ability and
/// makes composition easier. /// makes composition easier.
/// ///
/// ```ignore /// ```rust,ignore
/// struct MyService; /// struct MyService;
/// ///
/// impl Service for MyService { /// impl Service for MyService {
@@ -70,23 +60,26 @@ use self::ready::{err, ok, ready, Ready};
/// type Error = MyError; /// type Error = MyError;
/// type Future = Pin<Box<Future<Output=Result<Self::Response, Self::Error>>>>; /// type Future = Pin<Box<Future<Output=Result<Self::Response, Self::Error>>>>;
/// ///
/// fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { ... } /// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { ... }
/// ///
/// fn call(&self, req: Self::Request) -> Self::Future { ... } /// fn call(&mut self, req: Self::Request) -> Self::Future { ... }
/// } /// }
/// ``` /// ```
/// ///
/// Sometimes it is not necessary to implement the Service trait. For example, the above service /// Sometimes it is not necessary to implement the Service trait. For example, the above service
/// could be rewritten as a simple function and passed to [fn_service](fn_service()). /// could be rewritten as a simple function and passed to [fn_service](fn_service()).
/// ///
/// ```ignore /// ```rust,ignore
/// async fn my_service(req: u8) -> Result<u64, MyError>; /// async fn my_service(req: u8) -> Result<u64, MyError>;
/// ``` /// ```
pub trait Service<Req> { pub trait Service {
/// Requests handled by the service.
type Request;
/// Responses given by the service. /// Responses given by the service.
type Response; type Response;
/// Errors produced by the service when polling readiness or executing call. /// Errors produced by the service.
type Error; type Error;
/// The future response value. /// The future response value.
@@ -103,9 +96,9 @@ pub trait Service<Req> {
/// call and the next invocation of `call` results in an error. /// call and the next invocation of `call` results in an error.
/// ///
/// # Notes /// # Notes
/// 1. `poll_ready` might be called on a different task to `call`. /// 1. `.poll_ready()` might be called on different task from actual service call.
/// 1. In cases of chained services, `.poll_ready()` is called for all services at once. /// 1. In case of chained services, `.poll_ready()` get called for all services at once.
fn poll_ready(&self, ctx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>; fn poll_ready(&mut self, ctx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>;
/// Process the request and return the response asynchronously. /// Process the request and return the response asynchronously.
/// ///
@@ -116,7 +109,40 @@ pub trait Service<Req> {
/// ///
/// Calling `call` without calling `poll_ready` is permitted. The /// Calling `call` without calling `poll_ready` is permitted. The
/// implementation must be resilient to this fact. /// implementation must be resilient to this fact.
fn call(&self, req: Req) -> Self::Future; fn call(&mut self, req: Self::Request) -> Self::Future;
/// Map this service's output to a different type, returning a new service
/// of the resulting type.
///
/// This function is similar to the `Option::map` or `Iterator::map` where
/// it will change the type of the underlying service.
///
/// Note that this function consumes the receiving service and returns a
/// wrapped version of it, similar to the existing `map` methods in the
/// standard library.
fn map<F, R>(self, f: F) -> crate::dev::Map<Self, F, R>
where
Self: Sized,
F: FnMut(Self::Response) -> R,
{
crate::dev::Map::new(self, f)
}
/// Map this service's error to a different error, returning a new service.
///
/// This function is similar to the `Result::map_err` where it will change
/// the error type of the underlying service. For example, this can be useful to
/// ensure that services have the same error type.
///
/// Note that this function consumes the receiving service and returns a
/// wrapped version of it.
fn map_err<F, E>(self, f: F) -> crate::dev::MapErr<Self, F, E>
where
Self: Sized,
F: Fn(Self::Error) -> E,
{
crate::dev::MapErr::new(self, f)
}
} }
/// Factory for creating `Service`s. /// Factory for creating `Service`s.
@@ -128,7 +154,10 @@ pub trait Service<Req> {
/// requests on that new TCP stream. /// requests on that new TCP stream.
/// ///
/// `Config` is a service factory configuration type. /// `Config` is a service factory configuration type.
pub trait ServiceFactory<Req> { pub trait ServiceFactory {
/// Requests handled by the created services.
type Request;
/// Responses given by the created services. /// Responses given by the created services.
type Response; type Response;
@@ -139,7 +168,11 @@ pub trait ServiceFactory<Req> {
type Config; type Config;
/// The kind of `Service` created by this factory. /// The kind of `Service` created by this factory.
type Service: Service<Req, Response = Self::Response, Error = Self::Error>; type Service: Service<
Request = Self::Request,
Response = Self::Response,
Error = Self::Error,
>;
/// Errors potentially raised while building a service. /// Errors potentially raised while building a service.
type InitError; type InitError;
@@ -149,99 +182,113 @@ pub trait ServiceFactory<Req> {
/// Create and return a new service asynchronously. /// Create and return a new service asynchronously.
fn new_service(&self, cfg: Self::Config) -> Self::Future; fn new_service(&self, cfg: Self::Config) -> Self::Future;
/// Map this service's output to a different type, returning a new service
/// of the resulting type.
fn map<F, R>(self, f: F) -> crate::map::MapServiceFactory<Self, F, R>
where
Self: Sized,
F: FnMut(Self::Response) -> R + Clone,
{
crate::map::MapServiceFactory::new(self, f)
}
/// Map this service's error to a different error, returning a new service.
fn map_err<F, E>(self, f: F) -> crate::map_err::MapErrServiceFactory<Self, F, E>
where
Self: Sized,
F: Fn(Self::Error) -> E + Clone,
{
crate::map_err::MapErrServiceFactory::new(self, f)
}
/// Map this factory's init error to a different error, returning a new service.
fn map_init_err<F, E>(self, f: F) -> crate::map_init_err::MapInitErr<Self, F, E>
where
Self: Sized,
F: Fn(Self::InitError) -> E + Clone,
{
crate::map_init_err::MapInitErr::new(self, f)
}
} }
// TODO: remove implement on mut reference. impl<'a, S> Service for &'a mut S
impl<'a, S, Req> Service<Req> for &'a mut S
where where
S: Service<Req> + 'a, S: Service + 'a,
{ {
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Future = S::Future; type Future = S::Future;
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
(**self).poll_ready(ctx) (**self).poll_ready(ctx)
} }
fn call(&self, request: Req) -> S::Future { fn call(&mut self, request: Self::Request) -> S::Future {
(**self).call(request) (**self).call(request)
} }
} }
impl<'a, S, Req> Service<Req> for &'a S impl<S> Service for Box<S>
where where
S: Service<Req> + 'a, S: Service + ?Sized,
{ {
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Future = S::Future; type Future = S::Future;
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), S::Error>> {
(**self).poll_ready(ctx) (**self).poll_ready(ctx)
} }
fn call(&self, request: Req) -> S::Future { fn call(&mut self, request: Self::Request) -> S::Future {
(**self).call(request) (**self).call(request)
} }
} }
impl<S, Req> Service<Req> for Box<S> impl<S> Service for RefCell<S>
where where
S: Service<Req> + ?Sized, S: Service,
{ {
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Future = S::Future; type Future = S::Future;
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), S::Error>> { fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
(**self).poll_ready(ctx) self.borrow_mut().poll_ready(ctx)
} }
fn call(&self, request: Req) -> S::Future { fn call(&mut self, request: Self::Request) -> S::Future {
(**self).call(request) self.borrow_mut().call(request)
} }
} }
impl<S, Req> Service<Req> for Rc<S> impl<S> Service for Rc<RefCell<S>>
where where
S: Service<Req> + ?Sized, S: Service,
{ {
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Future = S::Future; type Future = S::Future;
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
(**self).poll_ready(ctx) self.borrow_mut().poll_ready(ctx)
} }
fn call(&self, request: Req) -> S::Future { fn call(&mut self, request: Self::Request) -> S::Future {
(**self).call(request) (&mut (**self).borrow_mut()).call(request)
} }
} }
/// This impl is deprecated since v2 because the `Service` trait now receives shared reference. impl<S> ServiceFactory for Rc<S>
impl<S, Req> Service<Req> for RefCell<S>
where where
S: Service<Req>, S: ServiceFactory,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.borrow().poll_ready(ctx)
}
fn call(&self, request: Req) -> S::Future {
self.borrow().call(request)
}
}
impl<S, Req> ServiceFactory<Req> for Rc<S>
where
S: ServiceFactory<Req>,
{ {
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Config = S::Config; type Config = S::Config;
@@ -254,10 +301,11 @@ where
} }
} }
impl<S, Req> ServiceFactory<Req> for Arc<S> impl<S> ServiceFactory for Arc<S>
where where
S: ServiceFactory<Req>, S: ServiceFactory,
{ {
type Request = S::Request;
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Config = S::Config; type Config = S::Config;
@@ -271,46 +319,59 @@ where
} }
/// Trait for types that can be converted to a `Service` /// Trait for types that can be converted to a `Service`
pub trait IntoService<S, Req> pub trait IntoService<T>
where where
S: Service<Req>, T: Service,
{ {
/// Convert to a `Service` /// Convert to a `Service`
fn into_service(self) -> S; fn into_service(self) -> T;
} }
/// Trait for types that can be converted to a `ServiceFactory` /// Trait for types that can be converted to a `ServiceFactory`
pub trait IntoServiceFactory<SF, Req> pub trait IntoServiceFactory<T>
where where
SF: ServiceFactory<Req>, T: ServiceFactory,
{ {
/// Convert `Self` to a `ServiceFactory` /// Convert `Self` to a `ServiceFactory`
fn into_factory(self) -> SF; fn into_factory(self) -> T;
} }
impl<S, Req> IntoService<S, Req> for S impl<T> IntoService<T> for T
where where
S: Service<Req>, T: Service,
{ {
fn into_service(self) -> S { fn into_service(self) -> T {
self self
} }
} }
impl<SF, Req> IntoServiceFactory<SF, Req> for SF impl<T> IntoServiceFactory<T> for T
where where
SF: ServiceFactory<Req>, T: ServiceFactory,
{ {
fn into_factory(self) -> SF { fn into_factory(self) -> T {
self self
} }
} }
/// Convert object of type `U` to a service `S` /// Convert object of type `T` to a service `S`
pub fn into_service<I, S, Req>(tp: I) -> S pub fn into_service<T, S>(tp: T) -> S
where where
I: IntoService<S, Req>, S: Service,
S: Service<Req>, T: IntoService<S>,
{ {
tp.into_service() tp.into_service()
} }
pub mod dev {
pub use crate::apply::{Apply, ApplyServiceFactory};
pub use crate::fn_service::{
FnService, FnServiceConfig, FnServiceFactory, FnServiceNoConfig,
};
pub use crate::map::{Map, MapServiceFactory};
pub use crate::map_config::{MapConfig, UnitConfig};
pub use crate::map_err::{MapErr, MapErrServiceFactory};
pub use crate::map_init_err::MapInitErr;
pub use crate::transform::ApplyTransform;
pub use crate::transform_err::TransformMapInitErr;
}

View File

@@ -1,181 +0,0 @@
/// A boilerplate implementation of [`Service::poll_ready`] that always signals readiness.
///
/// [`Service::poll_ready`]: crate::Service::poll_ready
///
/// # Examples
/// ```no_run
/// use actix_service::Service;
/// use futures_util::future::{ready, Ready};
///
/// struct IdentityService;
///
/// impl Service<u32> for IdentityService {
/// type Response = u32;
/// type Error = ();
/// type Future = Ready<Result<Self::Response, Self::Error>>;
///
/// actix_service::always_ready!();
///
/// fn call(&self, req: u32) -> Self::Future {
/// ready(Ok(req))
/// }
/// }
/// ```
#[macro_export]
macro_rules! always_ready {
() => {
#[inline]
fn poll_ready(
&self,
_: &mut ::core::task::Context<'_>,
) -> ::core::task::Poll<Result<(), Self::Error>> {
::core::task::Poll::Ready(Ok(()))
}
};
}
/// A boilerplate implementation of [`Service::poll_ready`] that forwards readiness checks to a
/// named struct field.
///
/// Tuple structs are not supported.
///
/// [`Service::poll_ready`]: crate::Service::poll_ready
///
/// # Examples
/// ```no_run
/// use actix_service::Service;
/// use futures_util::future::{ready, Ready};
///
/// struct WrapperService<S> {
/// inner: S,
/// }
///
/// impl<S> Service<()> for WrapperService<S>
/// where
/// S: Service<()>,
/// {
/// type Response = S::Response;
/// type Error = S::Error;
/// type Future = S::Future;
///
/// actix_service::forward_ready!(inner);
///
/// fn call(&self, req: ()) -> Self::Future {
/// self.inner.call(req)
/// }
/// }
/// ```
#[macro_export]
macro_rules! forward_ready {
($field:ident) => {
#[inline]
fn poll_ready(
&self,
cx: &mut ::core::task::Context<'_>,
) -> ::core::task::Poll<Result<(), Self::Error>> {
self.$field
.poll_ready(cx)
.map_err(::core::convert::Into::into)
}
};
}
#[cfg(test)]
mod tests {
use core::{
cell::Cell,
convert::Infallible,
task::{self, Context, Poll},
};
use futures_util::{
future::{ready, Ready},
task::noop_waker,
};
use crate::Service;
struct IdentityService;
impl Service<u32> for IdentityService {
type Response = u32;
type Error = Infallible;
type Future = Ready<Result<Self::Response, Self::Error>>;
always_ready!();
fn call(&self, req: u32) -> Self::Future {
ready(Ok(req))
}
}
struct CountdownService(Cell<u32>);
impl Service<()> for CountdownService {
type Response = ();
type Error = Infallible;
type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let count = self.0.get();
if count == 0 {
Poll::Ready(Ok(()))
} else {
self.0.set(count - 1);
cx.waker().wake_by_ref();
Poll::Pending
}
}
fn call(&self, _: ()) -> Self::Future {
ready(Ok(()))
}
}
struct WrapperService<S> {
inner: S,
}
impl<S> Service<()> for WrapperService<S>
where
S: Service<()>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
forward_ready!(inner);
fn call(&self, req: ()) -> Self::Future {
self.inner.call(req)
}
}
#[test]
fn test_always_ready_macro() {
let waker = noop_waker();
let mut cx = task::Context::from_waker(&waker);
let svc = IdentityService;
assert!(svc.poll_ready(&mut cx).is_ready());
assert!(svc.poll_ready(&mut cx).is_ready());
assert!(svc.poll_ready(&mut cx).is_ready());
}
#[test]
fn test_forward_ready_macro() {
let waker = noop_waker();
let mut cx = task::Context::from_waker(&waker);
let svc = WrapperService {
inner: CountdownService(Cell::new(3)),
};
assert!(svc.poll_ready(&mut cx).is_pending());
assert!(svc.poll_ready(&mut cx).is_pending());
assert!(svc.poll_ready(&mut cx).is_pending());
assert!(svc.poll_ready(&mut cx).is_ready());
}
}

View File

@@ -1,29 +1,25 @@
use core::{ use std::future::Future;
future::Future, use std::marker::PhantomData;
marker::PhantomData, use std::pin::Pin;
pin::Pin, use std::task::{Context, Poll};
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use super::{Service, ServiceFactory}; use super::{Service, ServiceFactory};
/// Service for the `map` combinator, changing the type of a service's response. /// Service for the `map` combinator, changing the type of a service's response.
/// ///
/// This is created by the `ServiceExt::map` method. /// This is created by the `ServiceExt::map` method.
pub struct Map<A, F, Req, Res> { pub struct Map<A, F, Response> {
service: A, service: A,
f: F, f: F,
_t: PhantomData<(Req, Res)>, _t: PhantomData<Response>,
} }
impl<A, F, Req, Res> Map<A, F, Req, Res> { impl<A, F, Response> Map<A, F, Response> {
/// Create new `Map` combinator /// Create new `Map` combinator
pub(crate) fn new(service: A, f: F) -> Self pub(crate) fn new(service: A, f: F) -> Self
where where
A: Service<Req>, A: Service,
F: FnMut(A::Response) -> Res, F: FnMut(A::Response) -> Response,
{ {
Self { Self {
service, service,
@@ -33,7 +29,7 @@ impl<A, F, Req, Res> Map<A, F, Req, Res> {
} }
} }
impl<A, F, Req, Res> Clone for Map<A, F, Req, Res> impl<A, F, Response> Clone for Map<A, F, Response>
where where
A: Clone, A: Clone,
F: Clone, F: Clone,
@@ -47,50 +43,52 @@ where
} }
} }
impl<A, F, Req, Res> Service<Req> for Map<A, F, Req, Res> impl<A, F, Response> Service for Map<A, F, Response>
where where
A: Service<Req>, A: Service,
F: FnMut(A::Response) -> Res + Clone, F: FnMut(A::Response) -> Response + Clone,
{ {
type Response = Res; type Request = A::Request;
type Response = Response;
type Error = A::Error; type Error = A::Error;
type Future = MapFuture<A, F, Req, Res>; type Future = MapFuture<A, F, Response>;
crate::forward_ready!(service); fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(ctx)
}
fn call(&self, req: Req) -> Self::Future { fn call(&mut self, req: A::Request) -> Self::Future {
MapFuture::new(self.service.call(req), self.f.clone()) MapFuture::new(self.service.call(req), self.f.clone())
} }
} }
pin_project! { #[pin_project::pin_project]
pub struct MapFuture<A, F, Req, Res> pub struct MapFuture<A, F, Response>
where where
A: Service<Req>, A: Service,
F: FnMut(A::Response) -> Res, F: FnMut(A::Response) -> Response,
{ {
f: F, f: F,
#[pin] #[pin]
fut: A::Future, fut: A::Future,
}
} }
impl<A, F, Req, Res> MapFuture<A, F, Req, Res> impl<A, F, Response> MapFuture<A, F, Response>
where where
A: Service<Req>, A: Service,
F: FnMut(A::Response) -> Res, F: FnMut(A::Response) -> Response,
{ {
fn new(fut: A::Future, f: F) -> Self { fn new(fut: A::Future, f: F) -> Self {
MapFuture { f, fut } MapFuture { f, fut }
} }
} }
impl<A, F, Req, Res> Future for MapFuture<A, F, Req, Res> impl<A, F, Response> Future for MapFuture<A, F, Response>
where where
A: Service<Req>, A: Service,
F: FnMut(A::Response) -> Res, F: FnMut(A::Response) -> Response,
{ {
type Output = Result<Res, A::Error>; type Output = Result<Response, A::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project(); let this = self.project();
@@ -104,17 +102,17 @@ where
} }
/// `MapNewService` new service combinator /// `MapNewService` new service combinator
pub struct MapServiceFactory<A, F, Req, Res> { pub struct MapServiceFactory<A, F, Res> {
a: A, a: A,
f: F, f: F,
r: PhantomData<(Res, Req)>, r: PhantomData<Res>,
} }
impl<A, F, Req, Res> MapServiceFactory<A, F, Req, Res> { impl<A, F, Res> MapServiceFactory<A, F, Res> {
/// Create new `Map` new service instance /// Create new `Map` new service instance
pub(crate) fn new(a: A, f: F) -> Self pub(crate) fn new(a: A, f: F) -> Self
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: FnMut(A::Response) -> Res, F: FnMut(A::Response) -> Res,
{ {
Self { Self {
@@ -125,7 +123,7 @@ impl<A, F, Req, Res> MapServiceFactory<A, F, Req, Res> {
} }
} }
impl<A, F, Req, Res> Clone for MapServiceFactory<A, F, Req, Res> impl<A, F, Res> Clone for MapServiceFactory<A, F, Res>
where where
A: Clone, A: Clone,
F: Clone, F: Clone,
@@ -139,39 +137,39 @@ where
} }
} }
impl<A, F, Req, Res> ServiceFactory<Req> for MapServiceFactory<A, F, Req, Res> impl<A, F, Res> ServiceFactory for MapServiceFactory<A, F, Res>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: FnMut(A::Response) -> Res + Clone, F: FnMut(A::Response) -> Res + Clone,
{ {
type Request = A::Request;
type Response = Res; type Response = Res;
type Error = A::Error; type Error = A::Error;
type Config = A::Config; type Config = A::Config;
type Service = Map<A::Service, F, Req, Res>; type Service = Map<A::Service, F, Res>;
type InitError = A::InitError; type InitError = A::InitError;
type Future = MapServiceFuture<A, F, Req, Res>; type Future = MapServiceFuture<A, F, Res>;
fn new_service(&self, cfg: A::Config) -> Self::Future { fn new_service(&self, cfg: A::Config) -> Self::Future {
MapServiceFuture::new(self.a.new_service(cfg), self.f.clone()) MapServiceFuture::new(self.a.new_service(cfg), self.f.clone())
} }
} }
pin_project! { #[pin_project::pin_project]
pub struct MapServiceFuture<A, F, Req, Res> pub struct MapServiceFuture<A, F, Res>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: FnMut(A::Response) -> Res, F: FnMut(A::Response) -> Res,
{ {
#[pin] #[pin]
fut: A::Future, fut: A::Future,
f: Option<F>, f: Option<F>,
}
} }
impl<A, F, Req, Res> MapServiceFuture<A, F, Req, Res> impl<A, F, Res> MapServiceFuture<A, F, Res>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: FnMut(A::Response) -> Res, F: FnMut(A::Response) -> Res,
{ {
fn new(fut: A::Future, f: F) -> Self { fn new(fut: A::Future, f: F) -> Self {
@@ -179,12 +177,12 @@ where
} }
} }
impl<A, F, Req, Res> Future for MapServiceFuture<A, F, Req, Res> impl<A, F, Res> Future for MapServiceFuture<A, F, Res>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: FnMut(A::Response) -> Res, F: FnMut(A::Response) -> Res,
{ {
type Output = Result<Map<A::Service, F, Req, Res>, A::InitError>; type Output = Result<Map<A::Service, F, Res>, A::InitError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project(); let this = self.project();
@@ -199,37 +197,38 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use futures_util::future::lazy; use futures_util::future::{lazy, ok, Ready};
use super::*; use super::*;
use crate::{ use crate::{IntoServiceFactory, Service, ServiceFactory};
ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory, ServiceFactoryExt,
};
struct Srv; struct Srv;
impl Service<()> for Srv { impl Service for Srv {
type Request = ();
type Response = (); type Response = ();
type Error = (); type Error = ();
type Future = Ready<Result<(), ()>>; type Future = Ready<Result<(), ()>>;
crate::always_ready!(); fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&self, _: ()) -> Self::Future { fn call(&mut self, _: ()) -> Self::Future {
ok(()) ok(())
} }
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_poll_ready() { async fn test_poll_ready() {
let srv = Srv.map(|_| "ok"); let mut srv = Srv.map(|_| "ok");
let res = lazy(|cx| srv.poll_ready(cx)).await; let res = lazy(|cx| srv.poll_ready(cx)).await;
assert_eq!(res, Poll::Ready(Ok(()))); assert_eq!(res, Poll::Ready(Ok(())));
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_call() { async fn test_call() {
let srv = Srv.map(|_| "ok"); let mut srv = Srv.map(|_| "ok");
let res = srv.call(()).await; let res = srv.call(()).await;
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), "ok"); assert_eq!(res.unwrap(), "ok");
@@ -238,7 +237,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_new_service() { async fn test_new_service() {
let new_srv = (|| ok::<_, ()>(Srv)).into_factory().map(|_| "ok"); let new_srv = (|| ok::<_, ()>(Srv)).into_factory().map(|_| "ok");
let srv = new_srv.new_service(&()).await.unwrap(); let mut srv = new_srv.new_service(&()).await.unwrap();
let res = srv.call(()).await; let res = srv.call(()).await;
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), ("ok")); assert_eq!(res.unwrap(), ("ok"));

View File

@@ -1,4 +1,4 @@
use core::marker::PhantomData; use std::marker::PhantomData;
use super::{IntoServiceFactory, ServiceFactory}; use super::{IntoServiceFactory, ServiceFactory};
@@ -6,123 +6,121 @@ use super::{IntoServiceFactory, ServiceFactory};
/// ///
/// Note that this function consumes the receiving service factory and returns /// Note that this function consumes the receiving service factory and returns
/// a wrapped version of it. /// a wrapped version of it.
pub fn map_config<I, SF, Req, F, Cfg>(factory: I, f: F) -> MapConfig<SF, Req, F, Cfg> pub fn map_config<T, U, F, C>(factory: U, f: F) -> MapConfig<T, F, C>
where where
I: IntoServiceFactory<SF, Req>, T: ServiceFactory,
SF: ServiceFactory<Req>, U: IntoServiceFactory<T>,
F: Fn(Cfg) -> SF::Config, F: Fn(C) -> T::Config,
{ {
MapConfig::new(factory.into_factory(), f) MapConfig::new(factory.into_factory(), f)
} }
/// Replace config with unit. /// Replace config with unit
pub fn unit_config<I, SF, Cfg, Req>(factory: I) -> UnitConfig<SF, Cfg, Req> pub fn unit_config<T, U, C>(factory: U) -> UnitConfig<T, C>
where where
I: IntoServiceFactory<SF, Req>, T: ServiceFactory<Config = ()>,
SF: ServiceFactory<Req, Config = ()>, U: IntoServiceFactory<T>,
{ {
UnitConfig::new(factory.into_factory()) UnitConfig::new(factory.into_factory())
} }
/// `map_config()` adapter service factory /// `map_config()` adapter service factory
pub struct MapConfig<SF, Req, F, Cfg> { pub struct MapConfig<A, F, C> {
factory: SF, a: A,
cfg_mapper: F, f: F,
e: PhantomData<(Cfg, Req)>, e: PhantomData<C>,
} }
impl<SF, Req, F, Cfg> MapConfig<SF, Req, F, Cfg> { impl<A, F, C> MapConfig<A, F, C> {
/// Create new `MapConfig` combinator /// Create new `MapConfig` combinator
pub(crate) fn new(factory: SF, cfg_mapper: F) -> Self pub(crate) fn new(a: A, f: F) -> Self
where where
SF: ServiceFactory<Req>, A: ServiceFactory,
F: Fn(Cfg) -> SF::Config, F: Fn(C) -> A::Config,
{ {
Self { Self {
factory, a,
cfg_mapper, f,
e: PhantomData, e: PhantomData,
} }
} }
} }
impl<SF, Req, F, Cfg> Clone for MapConfig<SF, Req, F, Cfg> impl<A, F, C> Clone for MapConfig<A, F, C>
where where
SF: Clone, A: Clone,
F: Clone, F: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
factory: self.factory.clone(), a: self.a.clone(),
cfg_mapper: self.cfg_mapper.clone(), f: self.f.clone(),
e: PhantomData, e: PhantomData,
} }
} }
} }
impl<SF, Req, F, Cfg> ServiceFactory<Req> for MapConfig<SF, Req, F, Cfg> impl<A, F, C> ServiceFactory for MapConfig<A, F, C>
where where
SF: ServiceFactory<Req>, A: ServiceFactory,
F: Fn(Cfg) -> SF::Config, F: Fn(C) -> A::Config,
{ {
type Response = SF::Response; type Request = A::Request;
type Error = SF::Error; type Response = A::Response;
type Error = A::Error;
type Config = Cfg; type Config = C;
type Service = SF::Service; type Service = A::Service;
type InitError = SF::InitError; type InitError = A::InitError;
type Future = SF::Future; type Future = A::Future;
fn new_service(&self, cfg: Self::Config) -> Self::Future { fn new_service(&self, cfg: C) -> Self::Future {
let mapped_cfg = (self.cfg_mapper)(cfg); self.a.new_service((self.f)(cfg))
self.factory.new_service(mapped_cfg)
} }
} }
/// `unit_config()` config combinator /// `unit_config()` config combinator
pub struct UnitConfig<SF, Cfg, Req> { pub struct UnitConfig<A, C> {
factory: SF, a: A,
_phantom: PhantomData<(Cfg, Req)>, e: PhantomData<C>,
} }
impl<SF, Cfg, Req> UnitConfig<SF, Cfg, Req> impl<A, C> UnitConfig<A, C>
where where
SF: ServiceFactory<Req, Config = ()>, A: ServiceFactory<Config = ()>,
{ {
/// Create new `UnitConfig` combinator /// Create new `UnitConfig` combinator
pub(crate) fn new(factory: SF) -> Self { pub(crate) fn new(a: A) -> Self {
Self { Self { a, e: PhantomData }
factory,
_phantom: PhantomData,
}
} }
} }
impl<SF, Cfg, Req> Clone for UnitConfig<SF, Cfg, Req> impl<A, C> Clone for UnitConfig<A, C>
where where
SF: Clone, A: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
factory: self.factory.clone(), a: self.a.clone(),
_phantom: PhantomData, e: PhantomData,
} }
} }
} }
impl<SF, Cfg, Req> ServiceFactory<Req> for UnitConfig<SF, Cfg, Req> impl<A, C> ServiceFactory for UnitConfig<A, C>
where where
SF: ServiceFactory<Req, Config = ()>, A: ServiceFactory<Config = ()>,
{ {
type Response = SF::Response; type Request = A::Request;
type Error = SF::Error; type Response = A::Response;
type Error = A::Error;
type Config = Cfg; type Config = C;
type Service = SF::Service; type Service = A::Service;
type InitError = SF::InitError; type InitError = A::InitError;
type Future = SF::Future; type Future = A::Future;
fn new_service(&self, _: Cfg) -> Self::Future { fn new_service(&self, _: C) -> Self::Future {
self.factory.new_service(()) self.a.new_service(())
} }
} }

View File

@@ -1,11 +1,7 @@
use core::{ use std::future::Future;
future::Future, use std::marker::PhantomData;
marker::PhantomData, use std::pin::Pin;
pin::Pin, use std::task::{Context, Poll};
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use super::{Service, ServiceFactory}; use super::{Service, ServiceFactory};
@@ -13,18 +9,18 @@ use super::{Service, ServiceFactory};
/// error. /// error.
/// ///
/// This is created by the `ServiceExt::map_err` method. /// This is created by the `ServiceExt::map_err` method.
pub struct MapErr<S, Req, F, E> { pub struct MapErr<A, F, E> {
service: S, service: A,
f: F, f: F,
_t: PhantomData<(E, Req)>, _t: PhantomData<E>,
} }
impl<S, Req, F, E> MapErr<S, Req, F, E> { impl<A, F, E> MapErr<A, F, E> {
/// Create new `MapErr` combinator /// Create new `MapErr` combinator
pub(crate) fn new(service: S, f: F) -> Self pub(crate) fn new(service: A, f: F) -> Self
where where
S: Service<Req>, A: Service,
F: Fn(S::Error) -> E, F: Fn(A::Error) -> E,
{ {
Self { Self {
service, service,
@@ -34,9 +30,9 @@ impl<S, Req, F, E> MapErr<S, Req, F, E> {
} }
} }
impl<S, Req, F, E> Clone for MapErr<S, Req, F, E> impl<A, F, E> Clone for MapErr<A, F, E>
where where
S: Clone, A: Clone,
F: Clone, F: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
@@ -48,39 +44,39 @@ where
} }
} }
impl<A, Req, F, E> Service<Req> for MapErr<A, Req, F, E> impl<A, F, E> Service for MapErr<A, F, E>
where where
A: Service<Req>, A: Service,
F: Fn(A::Error) -> E + Clone, F: Fn(A::Error) -> E + Clone,
{ {
type Request = A::Request;
type Response = A::Response; type Response = A::Response;
type Error = E; type Error = E;
type Future = MapErrFuture<A, Req, F, E>; type Future = MapErrFuture<A, F, E>;
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(ctx).map_err(&self.f) self.service.poll_ready(ctx).map_err(&self.f)
} }
fn call(&self, req: Req) -> Self::Future { fn call(&mut self, req: A::Request) -> Self::Future {
MapErrFuture::new(self.service.call(req), self.f.clone()) MapErrFuture::new(self.service.call(req), self.f.clone())
} }
} }
pin_project! { #[pin_project::pin_project]
pub struct MapErrFuture<A, Req, F, E> pub struct MapErrFuture<A, F, E>
where where
A: Service<Req>, A: Service,
F: Fn(A::Error) -> E, F: Fn(A::Error) -> E,
{ {
f: F, f: F,
#[pin] #[pin]
fut: A::Future, fut: A::Future,
}
} }
impl<A, Req, F, E> MapErrFuture<A, Req, F, E> impl<A, F, E> MapErrFuture<A, F, E>
where where
A: Service<Req>, A: Service,
F: Fn(A::Error) -> E, F: Fn(A::Error) -> E,
{ {
fn new(fut: A::Future, f: F) -> Self { fn new(fut: A::Future, f: F) -> Self {
@@ -88,9 +84,9 @@ where
} }
} }
impl<A, Req, F, E> Future for MapErrFuture<A, Req, F, E> impl<A, F, E> Future for MapErrFuture<A, F, E>
where where
A: Service<Req>, A: Service,
F: Fn(A::Error) -> E, F: Fn(A::Error) -> E,
{ {
type Output = Result<A::Response, E>; type Output = Result<A::Response, E>;
@@ -105,19 +101,19 @@ where
/// service's error. /// service's error.
/// ///
/// This is created by the `NewServiceExt::map_err` method. /// This is created by the `NewServiceExt::map_err` method.
pub struct MapErrServiceFactory<A, Req, F, E> pub struct MapErrServiceFactory<A, F, E>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: Fn(A::Error) -> E + Clone, F: Fn(A::Error) -> E + Clone,
{ {
a: A, a: A,
f: F, f: F,
e: PhantomData<(E, Req)>, e: PhantomData<E>,
} }
impl<A, Req, F, E> MapErrServiceFactory<A, Req, F, E> impl<A, F, E> MapErrServiceFactory<A, F, E>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: Fn(A::Error) -> E + Clone, F: Fn(A::Error) -> E + Clone,
{ {
/// Create new `MapErr` new service instance /// Create new `MapErr` new service instance
@@ -130,9 +126,9 @@ where
} }
} }
impl<A, Req, F, E> Clone for MapErrServiceFactory<A, Req, F, E> impl<A, F, E> Clone for MapErrServiceFactory<A, F, E>
where where
A: ServiceFactory<Req> + Clone, A: ServiceFactory + Clone,
F: Fn(A::Error) -> E + Clone, F: Fn(A::Error) -> E + Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
@@ -144,52 +140,52 @@ where
} }
} }
impl<A, Req, F, E> ServiceFactory<Req> for MapErrServiceFactory<A, Req, F, E> impl<A, F, E> ServiceFactory for MapErrServiceFactory<A, F, E>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: Fn(A::Error) -> E + Clone, F: Fn(A::Error) -> E + Clone,
{ {
type Request = A::Request;
type Response = A::Response; type Response = A::Response;
type Error = E; type Error = E;
type Config = A::Config; type Config = A::Config;
type Service = MapErr<A::Service, Req, F, E>; type Service = MapErr<A::Service, F, E>;
type InitError = A::InitError; type InitError = A::InitError;
type Future = MapErrServiceFuture<A, Req, F, E>; type Future = MapErrServiceFuture<A, F, E>;
fn new_service(&self, cfg: A::Config) -> Self::Future { fn new_service(&self, cfg: A::Config) -> Self::Future {
MapErrServiceFuture::new(self.a.new_service(cfg), self.f.clone()) MapErrServiceFuture::new(self.a.new_service(cfg), self.f.clone())
} }
} }
pin_project! { #[pin_project::pin_project]
pub struct MapErrServiceFuture<A, Req, F, E> pub struct MapErrServiceFuture<A, F, E>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: Fn(A::Error) -> E, F: Fn(A::Error) -> E,
{ {
#[pin] #[pin]
fut: A::Future, fut: A::Future,
f: F, f: F,
}
} }
impl<A, Req, F, E> MapErrServiceFuture<A, Req, F, E> impl<A, F, E> MapErrServiceFuture<A, F, E>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: Fn(A::Error) -> E, F: Fn(A::Error) -> E,
{ {
fn new(fut: A::Future, f: F) -> Self { fn new(fut: A::Future, f: F) -> Self {
MapErrServiceFuture { fut, f } MapErrServiceFuture { f, fut }
} }
} }
impl<A, Req, F, E> Future for MapErrServiceFuture<A, Req, F, E> impl<A, F, E> Future for MapErrServiceFuture<A, F, E>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: Fn(A::Error) -> E + Clone, F: Fn(A::Error) -> E + Clone,
{ {
type Output = Result<MapErr<A::Service, Req, F, E>, A::InitError>; type Output = Result<MapErr<A::Service, F, E>, A::InitError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project(); let this = self.project();
@@ -203,40 +199,38 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use futures_util::future::lazy; use futures_util::future::{err, lazy, ok, Ready};
use super::*; use super::*;
use crate::{ use crate::{IntoServiceFactory, Service, ServiceFactory};
err, ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory,
ServiceFactoryExt,
};
struct Srv; struct Srv;
impl Service<()> for Srv { impl Service for Srv {
type Request = ();
type Response = (); type Response = ();
type Error = (); type Error = ();
type Future = Ready<Result<(), ()>>; type Future = Ready<Result<(), ()>>;
fn poll_ready(&self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Err(())) Poll::Ready(Err(()))
} }
fn call(&self, _: ()) -> Self::Future { fn call(&mut self, _: ()) -> Self::Future {
err(()) err(())
} }
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_poll_ready() { async fn test_poll_ready() {
let srv = Srv.map_err(|_| "error"); let mut srv = Srv.map_err(|_| "error");
let res = lazy(|cx| srv.poll_ready(cx)).await; let res = lazy(|cx| srv.poll_ready(cx)).await;
assert_eq!(res, Poll::Ready(Err("error"))); assert_eq!(res, Poll::Ready(Err("error")));
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_call() { async fn test_call() {
let srv = Srv.map_err(|_| "error"); let mut srv = Srv.map_err(|_| "error");
let res = srv.call(()).await; let res = srv.call(()).await;
assert!(res.is_err()); assert!(res.is_err());
assert_eq!(res.err().unwrap(), "error"); assert_eq!(res.err().unwrap(), "error");
@@ -245,7 +239,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_new_service() { async fn test_new_service() {
let new_srv = (|| ok::<_, ()>(Srv)).into_factory().map_err(|_| "error"); let new_srv = (|| ok::<_, ()>(Srv)).into_factory().map_err(|_| "error");
let srv = new_srv.new_service(&()).await.unwrap(); let mut srv = new_srv.new_service(&()).await.unwrap();
let res = srv.call(()).await; let res = srv.call(()).await;
assert!(res.is_err()); assert!(res.is_err());
assert_eq!(res.err().unwrap(), "error"); assert_eq!(res.err().unwrap(), "error");

View File

@@ -1,25 +1,21 @@
use core::{ use std::future::Future;
future::Future, use std::marker::PhantomData;
marker::PhantomData, use std::pin::Pin;
pin::Pin, use std::task::{Context, Poll};
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use super::ServiceFactory; use super::ServiceFactory;
/// `MapInitErr` service combinator /// `MapInitErr` service combinator
pub struct MapInitErr<A, F, Req, Err> { pub struct MapInitErr<A, F, E> {
a: A, a: A,
f: F, f: F,
e: PhantomData<(Req, Err)>, e: PhantomData<E>,
} }
impl<A, F, Req, Err> MapInitErr<A, F, Req, Err> impl<A, F, E> MapInitErr<A, F, E>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: Fn(A::InitError) -> Err, F: Fn(A::InitError) -> E,
{ {
/// Create new `MapInitErr` combinator /// Create new `MapInitErr` combinator
pub(crate) fn new(a: A, f: F) -> Self { pub(crate) fn new(a: A, f: F) -> Self {
@@ -31,7 +27,7 @@ where
} }
} }
impl<A, F, Req, E> Clone for MapInitErr<A, F, Req, E> impl<A, F, E> Clone for MapInitErr<A, F, E>
where where
A: Clone, A: Clone,
F: Clone, F: Clone,
@@ -45,39 +41,39 @@ where
} }
} }
impl<A, F, Req, E> ServiceFactory<Req> for MapInitErr<A, F, Req, E> impl<A, F, E> ServiceFactory for MapInitErr<A, F, E>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: Fn(A::InitError) -> E + Clone, F: Fn(A::InitError) -> E + Clone,
{ {
type Request = A::Request;
type Response = A::Response; type Response = A::Response;
type Error = A::Error; type Error = A::Error;
type Config = A::Config; type Config = A::Config;
type Service = A::Service; type Service = A::Service;
type InitError = E; type InitError = E;
type Future = MapInitErrFuture<A, F, Req, E>; type Future = MapInitErrFuture<A, F, E>;
fn new_service(&self, cfg: A::Config) -> Self::Future { fn new_service(&self, cfg: A::Config) -> Self::Future {
MapInitErrFuture::new(self.a.new_service(cfg), self.f.clone()) MapInitErrFuture::new(self.a.new_service(cfg), self.f.clone())
} }
} }
pin_project! { #[pin_project::pin_project]
pub struct MapInitErrFuture<A, F, Req, E> pub struct MapInitErrFuture<A, F, E>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: Fn(A::InitError) -> E, F: Fn(A::InitError) -> E,
{ {
f: F, f: F,
#[pin] #[pin]
fut: A::Future, fut: A::Future,
}
} }
impl<A, F, Req, E> MapInitErrFuture<A, F, Req, E> impl<A, F, E> MapInitErrFuture<A, F, E>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: Fn(A::InitError) -> E, F: Fn(A::InitError) -> E,
{ {
fn new(fut: A::Future, f: F) -> Self { fn new(fut: A::Future, f: F) -> Self {
@@ -85,9 +81,9 @@ where
} }
} }
impl<A, F, Req, E> Future for MapInitErrFuture<A, F, Req, E> impl<A, F, E> Future for MapInitErrFuture<A, F, E>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
F: Fn(A::InitError) -> E, F: Fn(A::InitError) -> E,
{ {
type Output = Result<A::Service, E>; type Output = Result<A::Service, E>;

View File

@@ -1,9 +1,8 @@
use core::{ use std::future::Future;
marker::PhantomData, use std::task::{Context, Poll};
task::{Context, Poll},
};
use crate::and_then::{AndThenService, AndThenServiceFactory}; use crate::and_then::{AndThenService, AndThenServiceFactory};
use crate::and_then_apply_fn::{AndThenApplyFn, AndThenApplyFnFactory};
use crate::map::{Map, MapServiceFactory}; use crate::map::{Map, MapServiceFactory};
use crate::map_err::{MapErr, MapErrServiceFactory}; use crate::map_err::{MapErr, MapErrServiceFactory};
use crate::map_init_err::MapInitErr; use crate::map_init_err::MapInitErr;
@@ -11,39 +10,33 @@ use crate::then::{ThenService, ThenServiceFactory};
use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory}; use crate::{IntoService, IntoServiceFactory, Service, ServiceFactory};
/// Construct new pipeline with one service in pipeline chain. /// Construct new pipeline with one service in pipeline chain.
pub fn pipeline<I, S, Req>(service: I) -> Pipeline<S, Req> pub fn pipeline<F, T>(service: F) -> Pipeline<T>
where where
I: IntoService<S, Req>, F: IntoService<T>,
S: Service<Req>, T: Service,
{ {
Pipeline { Pipeline {
service: service.into_service(), service: service.into_service(),
_phantom: PhantomData,
} }
} }
/// Construct new pipeline factory with one service factory. /// Construct new pipeline factory with one service factory.
pub fn pipeline_factory<I, SF, Req>(factory: I) -> PipelineFactory<SF, Req> pub fn pipeline_factory<T, F>(factory: F) -> PipelineFactory<T>
where where
I: IntoServiceFactory<SF, Req>, T: ServiceFactory,
SF: ServiceFactory<Req>, F: IntoServiceFactory<T>,
{ {
PipelineFactory { PipelineFactory {
factory: factory.into_factory(), factory: factory.into_factory(),
_phantom: PhantomData,
} }
} }
/// Pipeline service - pipeline allows to compose multiple service into one service. /// Pipeline service - pipeline allows to compose multiple service into one service.
pub struct Pipeline<S, Req> { pub struct Pipeline<T> {
service: S, service: T,
_phantom: PhantomData<Req>,
} }
impl<S, Req> Pipeline<S, Req> impl<T: Service> Pipeline<T> {
where
S: Service<Req>,
{
/// Call another service after call to this one has resolved successfully. /// Call another service after call to this one has resolved successfully.
/// ///
/// This function can be used to chain two services together and ensure that /// This function can be used to chain two services together and ensure that
@@ -53,18 +46,41 @@ where
/// ///
/// Note that this function consumes the receiving service and returns a /// Note that this function consumes the receiving service and returns a
/// wrapped version of it. /// wrapped version of it.
pub fn and_then<I, S1>( pub fn and_then<F, U>(
self, self,
service: I, service: F,
) -> Pipeline<impl Service<Req, Response = S1::Response, Error = S::Error> + Clone, Req> ) -> Pipeline<
impl Service<Request = T::Request, Response = U::Response, Error = T::Error> + Clone,
>
where where
Self: Sized, Self: Sized,
I: IntoService<S1, S::Response>, F: IntoService<U>,
S1: Service<S::Response, Error = S::Error>, U: Service<Request = T::Response, Error = T::Error>,
{ {
Pipeline { Pipeline {
service: AndThenService::new(self.service, service.into_service()), service: AndThenService::new(self.service, service.into_service()),
_phantom: PhantomData, }
}
/// Apply function to specified service and use it as a next service in
/// chain.
///
/// Short version of `pipeline_factory(...).and_then(apply_fn_factory(...))`
pub fn and_then_apply_fn<U, I, F, Fut, Res, Err>(
self,
service: I,
f: F,
) -> Pipeline<impl Service<Request = T::Request, Response = Res, Error = Err> + Clone>
where
Self: Sized,
I: IntoService<U>,
U: Service,
F: FnMut(T::Response, &mut U) -> Fut,
Fut: Future<Output = Result<Res, Err>>,
Err: From<T::Error> + From<U::Error>,
{
Pipeline {
service: AndThenApplyFn::new(self.service, service.into_service(), f),
} }
} }
@@ -73,18 +89,19 @@ where
/// ///
/// Note that this function consumes the receiving pipeline and returns a /// Note that this function consumes the receiving pipeline and returns a
/// wrapped version of it. /// wrapped version of it.
pub fn then<F, S1>( pub fn then<F, U>(
self, self,
service: F, service: F,
) -> Pipeline<impl Service<Req, Response = S1::Response, Error = S::Error> + Clone, Req> ) -> Pipeline<
impl Service<Request = T::Request, Response = U::Response, Error = T::Error> + Clone,
>
where where
Self: Sized, Self: Sized,
F: IntoService<S1, Result<S::Response, S::Error>>, F: IntoService<U>,
S1: Service<Result<S::Response, S::Error>, Error = S::Error>, U: Service<Request = Result<T::Response, T::Error>, Error = T::Error>,
{ {
Pipeline { Pipeline {
service: ThenService::new(self.service, service.into_service()), service: ThenService::new(self.service, service.into_service()),
_phantom: PhantomData,
} }
} }
@@ -97,14 +114,13 @@ where
/// Note that this function consumes the receiving service and returns a /// Note that this function consumes the receiving service and returns a
/// wrapped version of it, similar to the existing `map` methods in the /// wrapped version of it, similar to the existing `map` methods in the
/// standard library. /// standard library.
pub fn map<F, R>(self, f: F) -> Pipeline<Map<S, F, Req, R>, Req> pub fn map<F, R>(self, f: F) -> Pipeline<Map<T, F, R>>
where where
Self: Sized, Self: Sized,
F: FnMut(S::Response) -> R, F: FnMut(T::Response) -> R,
{ {
Pipeline { Pipeline {
service: Map::new(self.service, f), service: Map::new(self.service, f),
_phantom: PhantomData,
} }
} }
@@ -116,85 +132,114 @@ where
/// ///
/// Note that this function consumes the receiving service and returns a /// Note that this function consumes the receiving service and returns a
/// wrapped version of it. /// wrapped version of it.
pub fn map_err<F, E>(self, f: F) -> Pipeline<MapErr<S, Req, F, E>, Req> pub fn map_err<F, E>(self, f: F) -> Pipeline<MapErr<T, F, E>>
where where
Self: Sized, Self: Sized,
F: Fn(S::Error) -> E, F: Fn(T::Error) -> E,
{ {
Pipeline { Pipeline {
service: MapErr::new(self.service, f), service: MapErr::new(self.service, f),
_phantom: PhantomData,
} }
} }
} }
impl<T, Req> Clone for Pipeline<T, Req> impl<T> Clone for Pipeline<T>
where where
T: Clone, T: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Pipeline { Pipeline {
service: self.service.clone(), service: self.service.clone(),
_phantom: PhantomData,
} }
} }
} }
impl<S: Service<Req>, Req> Service<Req> for Pipeline<S, Req> { impl<T: Service> Service for Pipeline<T> {
type Response = S::Response; type Request = T::Request;
type Error = S::Error; type Response = T::Response;
type Future = S::Future; type Error = T::Error;
type Future = T::Future;
#[inline] #[inline]
fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll<Result<(), S::Error>> { fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), T::Error>> {
self.service.poll_ready(ctx) self.service.poll_ready(ctx)
} }
#[inline] #[inline]
fn call(&self, req: Req) -> Self::Future { fn call(&mut self, req: T::Request) -> Self::Future {
self.service.call(req) self.service.call(req)
} }
} }
/// Pipeline factory /// Pipeline factory
pub struct PipelineFactory<SF, Req> { pub struct PipelineFactory<T> {
factory: SF, factory: T,
_phantom: PhantomData<Req>,
} }
impl<SF, Req> PipelineFactory<SF, Req> impl<T: ServiceFactory> PipelineFactory<T> {
where
SF: ServiceFactory<Req>,
{
/// Call another service after call to this one has resolved successfully. /// Call another service after call to this one has resolved successfully.
pub fn and_then<I, SF1>( pub fn and_then<F, U>(
self, self,
factory: I, factory: F,
) -> PipelineFactory< ) -> PipelineFactory<
impl ServiceFactory< impl ServiceFactory<
Req, Request = T::Request,
Response = SF1::Response, Response = U::Response,
Error = SF::Error, Error = T::Error,
Config = SF::Config, Config = T::Config,
InitError = SF::InitError, InitError = T::InitError,
Service = impl Service<Req, Response = SF1::Response, Error = SF::Error> + Clone, Service = impl Service<
Request = T::Request,
Response = U::Response,
Error = T::Error,
> + Clone,
> + Clone, > + Clone,
Req,
> >
where where
Self: Sized, Self: Sized,
SF::Config: Clone, T::Config: Clone,
I: IntoServiceFactory<SF1, SF::Response>, F: IntoServiceFactory<U>,
SF1: ServiceFactory< U: ServiceFactory<
SF::Response, Config = T::Config,
Config = SF::Config, Request = T::Response,
Error = SF::Error, Error = T::Error,
InitError = SF::InitError, InitError = T::InitError,
>, >,
{ {
PipelineFactory { PipelineFactory {
factory: AndThenServiceFactory::new(self.factory, factory.into_factory()), factory: AndThenServiceFactory::new(self.factory, factory.into_factory()),
_phantom: PhantomData, }
}
/// Apply function to specified service and use it as a next service in
/// chain.
///
/// Short version of `pipeline_factory(...).and_then(apply_fn_factory(...))`
pub fn and_then_apply_fn<U, I, F, Fut, Res, Err>(
self,
factory: I,
f: F,
) -> PipelineFactory<
impl ServiceFactory<
Request = T::Request,
Response = Res,
Error = Err,
Config = T::Config,
InitError = T::InitError,
Service = impl Service<Request = T::Request, Response = Res, Error = Err> + Clone,
> + Clone,
>
where
Self: Sized,
T::Config: Clone,
I: IntoServiceFactory<U>,
U: ServiceFactory<Config = T::Config, InitError = T::InitError>,
F: FnMut(T::Response, &mut U::Service) -> Fut + Clone,
Fut: Future<Output = Result<Res, Err>>,
Err: From<T::Error> + From<U::Error>,
{
PipelineFactory {
factory: AndThenApplyFnFactory::new(self.factory, factory.into_factory(), f),
} }
} }
@@ -204,103 +249,96 @@ where
/// ///
/// Note that this function consumes the receiving pipeline and returns a /// Note that this function consumes the receiving pipeline and returns a
/// wrapped version of it. /// wrapped version of it.
pub fn then<I, SF1>( pub fn then<F, U>(
self, self,
factory: I, factory: F,
) -> PipelineFactory< ) -> PipelineFactory<
impl ServiceFactory< impl ServiceFactory<
Req, Request = T::Request,
Response = SF1::Response, Response = U::Response,
Error = SF::Error, Error = T::Error,
Config = SF::Config, Config = T::Config,
InitError = SF::InitError, InitError = T::InitError,
Service = impl Service<Req, Response = SF1::Response, Error = SF::Error> + Clone, Service = impl Service<
Request = T::Request,
Response = U::Response,
Error = T::Error,
> + Clone,
> + Clone, > + Clone,
Req,
> >
where where
Self: Sized, Self: Sized,
SF::Config: Clone, T::Config: Clone,
I: IntoServiceFactory<SF1, Result<SF::Response, SF::Error>>, F: IntoServiceFactory<U>,
SF1: ServiceFactory< U: ServiceFactory<
Result<SF::Response, SF::Error>, Config = T::Config,
Config = SF::Config, Request = Result<T::Response, T::Error>,
Error = SF::Error, Error = T::Error,
InitError = SF::InitError, InitError = T::InitError,
>, >,
{ {
PipelineFactory { PipelineFactory {
factory: ThenServiceFactory::new(self.factory, factory.into_factory()), factory: ThenServiceFactory::new(self.factory, factory.into_factory()),
_phantom: PhantomData,
} }
} }
/// Map this service's output to a different type, returning a new service /// Map this service's output to a different type, returning a new service
/// of the resulting type. /// of the resulting type.
pub fn map<F, R>(self, f: F) -> PipelineFactory<MapServiceFactory<SF, F, Req, R>, Req> pub fn map<F, R>(self, f: F) -> PipelineFactory<MapServiceFactory<T, F, R>>
where where
Self: Sized, Self: Sized,
F: FnMut(SF::Response) -> R + Clone, F: FnMut(T::Response) -> R + Clone,
{ {
PipelineFactory { PipelineFactory {
factory: MapServiceFactory::new(self.factory, f), factory: MapServiceFactory::new(self.factory, f),
_phantom: PhantomData,
} }
} }
/// Map this service's error to a different error, returning a new service. /// Map this service's error to a different error, returning a new service.
pub fn map_err<F, E>( pub fn map_err<F, E>(self, f: F) -> PipelineFactory<MapErrServiceFactory<T, F, E>>
self,
f: F,
) -> PipelineFactory<MapErrServiceFactory<SF, Req, F, E>, Req>
where where
Self: Sized, Self: Sized,
F: Fn(SF::Error) -> E + Clone, F: Fn(T::Error) -> E + Clone,
{ {
PipelineFactory { PipelineFactory {
factory: MapErrServiceFactory::new(self.factory, f), factory: MapErrServiceFactory::new(self.factory, f),
_phantom: PhantomData,
} }
} }
/// Map this factory's init error to a different error, returning a new service. /// Map this factory's init error to a different error, returning a new service.
pub fn map_init_err<F, E>(self, f: F) -> PipelineFactory<MapInitErr<SF, F, Req, E>, Req> pub fn map_init_err<F, E>(self, f: F) -> PipelineFactory<MapInitErr<T, F, E>>
where where
Self: Sized, Self: Sized,
F: Fn(SF::InitError) -> E + Clone, F: Fn(T::InitError) -> E + Clone,
{ {
PipelineFactory { PipelineFactory {
factory: MapInitErr::new(self.factory, f), factory: MapInitErr::new(self.factory, f),
_phantom: PhantomData,
} }
} }
} }
impl<T, Req> Clone for PipelineFactory<T, Req> impl<T> Clone for PipelineFactory<T>
where where
T: Clone, T: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
PipelineFactory { PipelineFactory {
factory: self.factory.clone(), factory: self.factory.clone(),
_phantom: PhantomData,
} }
} }
} }
impl<SF, Req> ServiceFactory<Req> for PipelineFactory<SF, Req> impl<T: ServiceFactory> ServiceFactory for PipelineFactory<T> {
where type Config = T::Config;
SF: ServiceFactory<Req>, type Request = T::Request;
{ type Response = T::Response;
type Config = SF::Config; type Error = T::Error;
type Response = SF::Response; type Service = T::Service;
type Error = SF::Error; type InitError = T::InitError;
type Service = SF::Service; type Future = T::Future;
type InitError = SF::InitError;
type Future = SF::Future;
#[inline] #[inline]
fn new_service(&self, cfg: SF::Config) -> Self::Future { fn new_service(&self, cfg: T::Config) -> Self::Future {
self.factory.new_service(cfg) self.factory.new_service(cfg)
} }
} }

View File

@@ -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<T> {
val: Option<T>,
}
impl<T> Ready<T> {
/// Unwraps the value from this immediately ready future.
#[inline]
pub fn into_inner(mut self) -> T {
self.val.take().unwrap()
}
}
impl<T> Unpin for Ready<T> {}
impl<T> Future for Ready<T> {
type Output = T;
#[inline]
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<T> {
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<T>(val: T) -> Ready<T> {
Ready { val: Some(val) }
}
/// Create a future that is immediately ready with a success value.
#[allow(dead_code)]
pub(crate) fn ok<T, E>(val: T) -> Ready<Result<T, E>> {
Ready { val: Some(Ok(val)) }
}
/// Create a future that is immediately ready with an error value.
#[allow(dead_code)]
pub(crate) fn err<T, E>(err: E) -> Ready<Result<T, E>> {
Ready {
val: Some(Err(err)),
}
}

View File

@@ -1,13 +1,8 @@
use alloc::rc::Rc; use std::cell::RefCell;
use core::{ use std::future::Future;
future::Future, use std::pin::Pin;
marker::PhantomData, use std::rc::Rc;
pin::Pin, use std::task::{Context, Poll};
task::{Context, Poll},
};
use futures_core::ready;
use pin_project_lite::pin_project;
use super::{Service, ServiceFactory}; use super::{Service, ServiceFactory};
@@ -15,81 +10,77 @@ use super::{Service, ServiceFactory};
/// another service. /// another service.
/// ///
/// This is created by the `Pipeline::then` method. /// This is created by the `Pipeline::then` method.
pub(crate) struct ThenService<A, B, Req>(Rc<(A, B)>, PhantomData<Req>); pub(crate) struct ThenService<A, B>(Rc<RefCell<(A, B)>>);
impl<A, B, Req> ThenService<A, B, Req> { impl<A, B> ThenService<A, B> {
/// Create new `.then()` combinator /// Create new `.then()` combinator
pub(crate) fn new(a: A, b: B) -> ThenService<A, B, Req> pub(crate) fn new(a: A, b: B) -> ThenService<A, B>
where where
A: Service<Req>, A: Service,
B: Service<Result<A::Response, A::Error>, Error = A::Error>, B: Service<Request = Result<A::Response, A::Error>, Error = A::Error>,
{ {
Self(Rc::new((a, b)), PhantomData) Self(Rc::new(RefCell::new((a, b))))
} }
} }
impl<A, B, Req> Clone for ThenService<A, B, Req> { impl<A, B> Clone for ThenService<A, B> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
ThenService(self.0.clone(), PhantomData) ThenService(self.0.clone())
} }
} }
impl<A, B, Req> Service<Req> for ThenService<A, B, Req> impl<A, B> Service for ThenService<A, B>
where where
A: Service<Req>, A: Service,
B: Service<Result<A::Response, A::Error>, Error = A::Error>, B: Service<Request = Result<A::Response, A::Error>, Error = A::Error>,
{ {
type Request = A::Request;
type Response = B::Response; type Response = B::Response;
type Error = B::Error; type Error = B::Error;
type Future = ThenServiceResponse<A, B, Req>; type Future = ThenServiceResponse<A, B>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let (a, b) = &*self.0; let mut srv = self.0.borrow_mut();
let not_ready = !a.poll_ready(cx)?.is_ready(); let not_ready = !srv.0.poll_ready(cx)?.is_ready();
if !b.poll_ready(cx)?.is_ready() || not_ready { if !srv.1.poll_ready(cx)?.is_ready() || not_ready {
Poll::Pending Poll::Pending
} else { } else {
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} }
} }
fn call(&self, req: Req) -> Self::Future { fn call(&mut self, req: A::Request) -> Self::Future {
ThenServiceResponse { ThenServiceResponse {
state: State::A { state: State::A(self.0.borrow_mut().0.call(req), Some(self.0.clone())),
fut: self.0 .0.call(req),
b: Some(self.0.clone()),
},
} }
} }
} }
pin_project! { #[pin_project::pin_project]
pub(crate) struct ThenServiceResponse<A, B, Req> pub(crate) struct ThenServiceResponse<A, B>
where
A: Service<Req>,
B: Service<Result<A::Response, A::Error>>,
{
#[pin]
state: State<A, B, Req>,
}
}
pin_project! {
#[project = StateProj]
enum State<A, B, Req>
where
A: Service<Req>,
B: Service<Result<A::Response, A::Error>>,
{
A { #[pin] fut: A::Future, b: Option<Rc<(A, B)>> },
B { #[pin] fut: B::Future },
}
}
impl<A, B, Req> Future for ThenServiceResponse<A, B, Req>
where where
A: Service<Req>, A: Service,
B: Service<Result<A::Response, A::Error>>, B: Service<Request = Result<A::Response, A::Error>>,
{
#[pin]
state: State<A, B>,
}
#[pin_project::pin_project(project = StateProj)]
enum State<A, B>
where
A: Service,
B: Service<Request = Result<A::Response, A::Error>>,
{
A(#[pin] A::Future, Option<Rc<RefCell<(A, B)>>>),
B(#[pin] B::Future),
Empty,
}
impl<A, B> Future for ThenServiceResponse<A, B>
where
A: Service,
B: Service<Request = Result<A::Response, A::Error>>,
{ {
type Output = Result<B::Response, B::Error>; type Output = Result<B::Response, B::Error>;
@@ -97,56 +88,66 @@ where
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
match this.state.as_mut().project() { match this.state.as_mut().project() {
StateProj::A { fut, b } => { StateProj::A(fut, b) => match fut.poll(cx) {
let res = ready!(fut.poll(cx)); Poll::Ready(res) => {
let b = b.take().unwrap(); let b = b.take().unwrap();
let fut = b.1.call(res); this.state.set(State::Empty); // drop fut A
this.state.set(State::B { fut }); let fut = b.borrow_mut().1.call(res);
self.poll(cx) this.state.set(State::B(fut));
self.poll(cx)
}
Poll::Pending => Poll::Pending,
},
StateProj::B(fut) => fut.poll(cx).map(|r| {
this.state.set(State::Empty);
r
}),
StateProj::Empty => {
panic!("future must not be polled after it returned `Poll::Ready`")
} }
StateProj::B { fut } => fut.poll(cx),
} }
} }
} }
/// `.then()` service factory combinator /// `.then()` service factory combinator
pub(crate) struct ThenServiceFactory<A, B, Req>(Rc<(A, B)>, PhantomData<Req>); pub(crate) struct ThenServiceFactory<A, B>(Rc<(A, B)>);
impl<A, B, Req> ThenServiceFactory<A, B, Req> impl<A, B> ThenServiceFactory<A, B>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
A::Config: Clone, A::Config: Clone,
B: ServiceFactory< B: ServiceFactory<
Result<A::Response, A::Error>,
Config = A::Config, Config = A::Config,
Request = Result<A::Response, A::Error>,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
{ {
/// Create new `AndThen` combinator /// Create new `AndThen` combinator
pub(crate) fn new(a: A, b: B) -> Self { pub(crate) fn new(a: A, b: B) -> Self {
Self(Rc::new((a, b)), PhantomData) Self(Rc::new((a, b)))
} }
} }
impl<A, B, Req> ServiceFactory<Req> for ThenServiceFactory<A, B, Req> impl<A, B> ServiceFactory for ThenServiceFactory<A, B>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
A::Config: Clone, A::Config: Clone,
B: ServiceFactory< B: ServiceFactory<
Result<A::Response, A::Error>,
Config = A::Config, Config = A::Config,
Request = Result<A::Response, A::Error>,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
{ {
type Request = A::Request;
type Response = B::Response; type Response = B::Response;
type Error = A::Error; type Error = A::Error;
type Config = A::Config; type Config = A::Config;
type Service = ThenService<A::Service, B::Service, Req>; type Service = ThenService<A::Service, B::Service>;
type InitError = A::InitError; type InitError = A::InitError;
type Future = ThenServiceFactoryResponse<A, B, Req>; type Future = ThenServiceFactoryResponse<A, B>;
fn new_service(&self, cfg: A::Config) -> Self::Future { fn new_service(&self, cfg: A::Config) -> Self::Future {
let srv = &*self.0; let srv = &*self.0;
@@ -154,38 +155,37 @@ where
} }
} }
impl<A, B, Req> Clone for ThenServiceFactory<A, B, Req> { impl<A, B> Clone for ThenServiceFactory<A, B> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(self.0.clone(), PhantomData) Self(self.0.clone())
} }
} }
pin_project! { #[pin_project::pin_project]
pub(crate) struct ThenServiceFactoryResponse<A, B, Req> pub(crate) struct ThenServiceFactoryResponse<A, B>
where
A: ServiceFactory<Req>,
B: ServiceFactory<
Result<A::Response, A::Error>,
Config = A::Config,
Error = A::Error,
InitError = A::InitError,
>,
{
#[pin]
fut_b: B::Future,
#[pin]
fut_a: A::Future,
a: Option<A::Service>,
b: Option<B::Service>,
}
}
impl<A, B, Req> ThenServiceFactoryResponse<A, B, Req>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
B: ServiceFactory< B: ServiceFactory<
Result<A::Response, A::Error>,
Config = A::Config, Config = A::Config,
Request = Result<A::Response, A::Error>,
Error = A::Error,
InitError = A::InitError,
>,
{
#[pin]
fut_b: B::Future,
#[pin]
fut_a: A::Future,
a: Option<A::Service>,
b: Option<B::Service>,
}
impl<A, B> ThenServiceFactoryResponse<A, B>
where
A: ServiceFactory,
B: ServiceFactory<
Config = A::Config,
Request = Result<A::Response, A::Error>,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
@@ -200,17 +200,17 @@ where
} }
} }
impl<A, B, Req> Future for ThenServiceFactoryResponse<A, B, Req> impl<A, B> Future for ThenServiceFactoryResponse<A, B>
where where
A: ServiceFactory<Req>, A: ServiceFactory,
B: ServiceFactory< B: ServiceFactory<
Result<A::Response, A::Error>,
Config = A::Config, Config = A::Config,
Request = Result<A::Response, A::Error>,
Error = A::Error, Error = A::Error,
InitError = A::InitError, InitError = A::InitError,
>, >,
{ {
type Output = Result<ThenService<A::Service, B::Service, Req>, A::InitError>; type Output = Result<ThenService<A::Service, B::Service>, A::InitError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project(); let this = self.project();
@@ -238,30 +238,29 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use alloc::rc::Rc; use std::cell::Cell;
use core::{ use std::rc::Rc;
cell::Cell, use std::task::{Context, Poll};
task::{Context, Poll},
};
use futures_util::future::lazy; use futures_util::future::{err, lazy, ok, ready, Ready};
use crate::{err, ok, pipeline, pipeline_factory, ready, Ready, Service, ServiceFactory}; use crate::{pipeline, pipeline_factory, Service, ServiceFactory};
#[derive(Clone)] #[derive(Clone)]
struct Srv1(Rc<Cell<usize>>); struct Srv1(Rc<Cell<usize>>);
impl Service<Result<&'static str, &'static str>> for Srv1 { impl Service for Srv1 {
type Request = Result<&'static str, &'static str>;
type Response = &'static str; type Response = &'static str;
type Error = (); type Error = ();
type Future = Ready<Result<Self::Response, Self::Error>>; type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.set(self.0.get() + 1); self.0.set(self.0.get() + 1);
Poll::Ready(Ok(())) Poll::Ready(Ok(()))
} }
fn call(&self, req: Result<&'static str, &'static str>) -> Self::Future { fn call(&mut self, req: Result<&'static str, &'static str>) -> Self::Future {
match req { match req {
Ok(msg) => ok(msg), Ok(msg) => ok(msg),
Err(_) => err(()), Err(_) => err(()),
@@ -271,17 +270,18 @@ mod tests {
struct Srv2(Rc<Cell<usize>>); struct Srv2(Rc<Cell<usize>>);
impl Service<Result<&'static str, ()>> for Srv2 { impl Service for Srv2 {
type Request = Result<&'static str, ()>;
type Response = (&'static str, &'static str); type Response = (&'static str, &'static str);
type Error = (); type Error = ();
type Future = Ready<Result<Self::Response, ()>>; type Future = Ready<Result<Self::Response, ()>>;
fn poll_ready(&self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.set(self.0.get() + 1); self.0.set(self.0.get() + 1);
Poll::Ready(Err(())) Poll::Ready(Err(()))
} }
fn call(&self, req: Result<&'static str, ()>) -> Self::Future { fn call(&mut self, req: Result<&'static str, ()>) -> Self::Future {
match req { match req {
Ok(msg) => ok((msg, "ok")), Ok(msg) => ok((msg, "ok")),
Err(()) => ok(("srv2", "err")), Err(()) => ok(("srv2", "err")),
@@ -292,7 +292,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_poll_ready() { async fn test_poll_ready() {
let cnt = Rc::new(Cell::new(0)); let cnt = Rc::new(Cell::new(0));
let srv = pipeline(Srv1(cnt.clone())).then(Srv2(cnt.clone())); let mut srv = pipeline(Srv1(cnt.clone())).then(Srv2(cnt.clone()));
let res = lazy(|cx| srv.poll_ready(cx)).await; let res = lazy(|cx| srv.poll_ready(cx)).await;
assert_eq!(res, Poll::Ready(Err(()))); assert_eq!(res, Poll::Ready(Err(())));
assert_eq!(cnt.get(), 2); assert_eq!(cnt.get(), 2);
@@ -301,7 +301,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_call() { async fn test_call() {
let cnt = Rc::new(Cell::new(0)); let cnt = Rc::new(Cell::new(0));
let srv = pipeline(Srv1(cnt.clone())).then(Srv2(cnt)); let mut srv = pipeline(Srv1(cnt.clone())).then(Srv2(cnt));
let res = srv.call(Ok("srv1")).await; let res = srv.call(Ok("srv1")).await;
assert!(res.is_ok()); assert!(res.is_ok());
@@ -318,7 +318,7 @@ mod tests {
let cnt2 = cnt.clone(); let cnt2 = cnt.clone();
let blank = move || ready(Ok::<_, ()>(Srv1(cnt2.clone()))); let blank = move || ready(Ok::<_, ()>(Srv1(cnt2.clone())));
let factory = pipeline_factory(blank).then(move || ready(Ok(Srv2(cnt.clone())))); let factory = pipeline_factory(blank).then(move || ready(Ok(Srv2(cnt.clone()))));
let srv = factory.new_service(&()).await.unwrap(); let mut srv = factory.new_service(&()).await.unwrap();
let res = srv.call(Ok("srv1")).await; let res = srv.call(Ok("srv1")).await;
assert!(res.is_ok()); assert!(res.is_ok());
assert_eq!(res.unwrap(), ("srv1", "ok")); assert_eq!(res.unwrap(), ("srv1", "ok"));

View File

@@ -1,22 +1,18 @@
use alloc::{rc::Rc, sync::Arc}; use std::future::Future;
use core::{ use std::pin::Pin;
future::Future, use std::rc::Rc;
marker::PhantomData, use std::sync::Arc;
pin::Pin, use std::task::{Context, Poll};
task::{Context, Poll},
};
use futures_core::ready;
use pin_project_lite::pin_project;
use crate::transform_err::TransformMapInitErr;
use crate::{IntoServiceFactory, Service, ServiceFactory}; use crate::{IntoServiceFactory, Service, ServiceFactory};
/// Apply a [`Transform`] to a [`Service`]. /// Apply transform to a service.
pub fn apply<T, S, I, Req>(t: T, factory: I) -> ApplyTransform<T, S, Req> pub fn apply<T, S, U>(t: T, factory: U) -> ApplyTransform<T, S>
where where
I: IntoServiceFactory<S, Req>, S: ServiceFactory,
S: ServiceFactory<Req>, T: Transform<S::Service, InitError = S::InitError>,
T: Transform<S::Service, Req, InitError = S::InitError>, U: IntoServiceFactory<S>,
{ {
ApplyTransform::new(t, factory.into_factory()) ApplyTransform::new(t, factory.into_factory())
} }
@@ -24,12 +20,13 @@ where
/// The `Transform` trait defines the interface of a service factory that wraps inner service /// The `Transform` trait defines the interface of a service factory that wraps inner service
/// during construction. /// during construction.
/// ///
/// Transform(middleware) wraps inner service and runs during inbound and/or outbound processing in /// Transform(middleware) wraps inner service and runs during
/// the request/response lifecycle. It may modify request and/or response. /// inbound and/or outbound processing in the request/response lifecycle.
/// It may modify request and/or response.
/// ///
/// For example, timeout transform: /// For example, timeout transform:
/// ///
/// ```ignore /// ```rust,ignore
/// pub struct Timeout<S> { /// pub struct Timeout<S> {
/// service: S, /// service: S,
/// timeout: Duration, /// timeout: Duration,
@@ -44,28 +41,31 @@ where
/// type Error = TimeoutError<S::Error>; /// type Error = TimeoutError<S::Error>;
/// type Future = TimeoutServiceResponse<S>; /// type Future = TimeoutServiceResponse<S>;
/// ///
/// actix_service::forward_ready!(service); /// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
/// ready!(self.service.poll_ready(cx)).map_err(TimeoutError::Service)
/// }
/// ///
/// fn call(&self, req: S::Request) -> Self::Future { /// fn call(&mut self, req: S::Request) -> Self::Future {
/// TimeoutServiceResponse { /// TimeoutServiceResponse {
/// fut: self.service.call(req), /// fut: self.service.call(req),
/// sleep: Sleep::new(clock::now() + self.timeout), /// sleep: Delay::new(clock::now() + self.timeout),
/// } /// }
/// } /// }
/// } /// }
/// ``` /// ```
/// ///
/// Timeout service in above example is decoupled from underlying service implementation and could /// Timeout service in above example is decoupled from underlying service implementation
/// be applied to any service. /// and could be applied to any service.
/// ///
/// The `Transform` trait defines the interface of a Service factory. `Transform` is often /// The `Transform` trait defines the interface of a Service factory. `Transform`
/// implemented for middleware, defining how to construct a middleware Service. A Service that is /// is often implemented for middleware, defining how to construct a
/// constructed by the factory takes the Service that follows it during execution as a parameter, /// middleware Service. A Service that is constructed by the factory takes
/// assuming ownership of the next Service. /// the Service that follows it during execution as a parameter, assuming
/// ownership of the next Service.
/// ///
/// Factory for `Timeout` middleware from the above example could look like this: /// Factory for `Timeout` middleware from the above example could look like this:
/// ///
/// ```ignore /// ```rust,,ignore
/// pub struct TimeoutTransform { /// pub struct TimeoutTransform {
/// timeout: Duration, /// timeout: Duration,
/// } /// }
@@ -82,22 +82,29 @@ where
/// type Future = Ready<Result<Self::Transform, Self::InitError>>; /// type Future = Ready<Result<Self::Transform, Self::InitError>>;
/// ///
/// fn new_transform(&self, service: S) -> Self::Future { /// fn new_transform(&self, service: S) -> Self::Future {
/// ready(Ok(TimeoutService { /// ok(TimeoutService {
/// service, /// service,
/// timeout: self.timeout, /// timeout: self.timeout,
/// })) /// })
/// } /// }
/// } /// }
/// ``` /// ```
pub trait Transform<S, Req> { pub trait Transform<S> {
/// Responses produced by the service. /// Requests handled by the service.
type Request;
/// Responses given by the service.
type Response; type Response;
/// Errors produced by the service. /// Errors produced by the service.
type Error; type Error;
/// The `TransformService` value created by this factory /// The `TransformService` value created by this factory
type Transform: Service<Req, Response = Self::Response, Error = Self::Error>; type Transform: Service<
Request = Self::Request,
Response = Self::Response,
Error = Self::Error,
>;
/// Errors produced while building a transform service. /// Errors produced while building a transform service.
type InitError; type InitError;
@@ -107,16 +114,27 @@ pub trait Transform<S, Req> {
/// Creates and returns a new Transform component, asynchronously /// Creates and returns a new Transform component, asynchronously
fn new_transform(&self, service: S) -> Self::Future; fn new_transform(&self, service: S) -> Self::Future;
/// Map this transform's factory error to a different error,
/// returning a new transform service factory.
fn map_init_err<F, E>(self, f: F) -> TransformMapInitErr<Self, S, F, E>
where
Self: Sized,
F: Fn(Self::InitError) -> E + Clone,
{
TransformMapInitErr::new(self, f)
}
} }
impl<T, S, Req> Transform<S, Req> for Rc<T> impl<T, S> Transform<S> for Rc<T>
where where
T: Transform<S, Req>, T: Transform<S>,
{ {
type Request = T::Request;
type Response = T::Response; type Response = T::Response;
type Error = T::Error; type Error = T::Error;
type Transform = T::Transform;
type InitError = T::InitError; type InitError = T::InitError;
type Transform = T::Transform;
type Future = T::Future; type Future = T::Future;
fn new_transform(&self, service: S) -> T::Future { fn new_transform(&self, service: S) -> T::Future {
@@ -124,14 +142,15 @@ where
} }
} }
impl<T, S, Req> Transform<S, Req> for Arc<T> impl<T, S> Transform<S> for Arc<T>
where where
T: Transform<S, Req>, T: Transform<S>,
{ {
type Request = T::Request;
type Response = T::Response; type Response = T::Response;
type Error = T::Error; type Error = T::Error;
type Transform = T::Transform;
type InitError = T::InitError; type InitError = T::InitError;
type Transform = T::Transform;
type Future = T::Future; type Future = T::Future;
fn new_transform(&self, service: S) -> T::Future { fn new_transform(&self, service: S) -> T::Future {
@@ -139,77 +158,73 @@ where
} }
} }
/// Apply a [`Transform`] to a [`Service`]. /// `Apply` transform to new service
pub struct ApplyTransform<T, S, Req>(Rc<(T, S)>, PhantomData<Req>); pub struct ApplyTransform<T, S>(Rc<(T, S)>);
impl<T, S, Req> ApplyTransform<T, S, Req> impl<T, S> ApplyTransform<T, S>
where where
S: ServiceFactory<Req>, S: ServiceFactory,
T: Transform<S::Service, Req, InitError = S::InitError>, T: Transform<S::Service, InitError = S::InitError>,
{ {
/// Create new `ApplyTransform` new service instance /// Create new `ApplyTransform` new service instance
fn new(t: T, service: S) -> Self { fn new(t: T, service: S) -> Self {
Self(Rc::new((t, service)), PhantomData) Self(Rc::new((t, service)))
} }
} }
impl<T, S, Req> Clone for ApplyTransform<T, S, Req> { impl<T, S> Clone for ApplyTransform<T, S> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
ApplyTransform(self.0.clone(), PhantomData) ApplyTransform(self.0.clone())
} }
} }
impl<T, S, Req> ServiceFactory<Req> for ApplyTransform<T, S, Req> impl<T, S> ServiceFactory for ApplyTransform<T, S>
where where
S: ServiceFactory<Req>, S: ServiceFactory,
T: Transform<S::Service, Req, InitError = S::InitError>, T: Transform<S::Service, InitError = S::InitError>,
{ {
type Request = T::Request;
type Response = T::Response; type Response = T::Response;
type Error = T::Error; type Error = T::Error;
type Config = S::Config; type Config = S::Config;
type Service = T::Transform; type Service = T::Transform;
type InitError = T::InitError; type InitError = T::InitError;
type Future = ApplyTransformFuture<T, S, Req>; type Future = ApplyTransformFuture<T, S>;
fn new_service(&self, cfg: S::Config) -> Self::Future { fn new_service(&self, cfg: S::Config) -> Self::Future {
ApplyTransformFuture { ApplyTransformFuture {
store: self.0.clone(), store: self.0.clone(),
state: ApplyTransformFutureState::A { state: ApplyTransformFutureState::A(self.0.as_ref().1.new_service(cfg)),
fut: self.0.as_ref().1.new_service(cfg),
},
} }
} }
} }
pin_project! { #[pin_project::pin_project]
pub struct ApplyTransformFuture<T, S, Req> pub struct ApplyTransformFuture<T, S>
where
S: ServiceFactory<Req>,
T: Transform<S::Service, Req, InitError = S::InitError>,
{
store: Rc<(T, S)>,
#[pin]
state: ApplyTransformFutureState<T, S, Req>,
}
}
pin_project! {
#[project = ApplyTransformFutureStateProj]
pub enum ApplyTransformFutureState<T, S, Req>
where
S: ServiceFactory<Req>,
T: Transform<S::Service, Req, InitError = S::InitError>,
{
A { #[pin] fut: S::Future },
B { #[pin] fut: T::Future },
}
}
impl<T, S, Req> Future for ApplyTransformFuture<T, S, Req>
where where
S: ServiceFactory<Req>, S: ServiceFactory,
T: Transform<S::Service, Req, InitError = S::InitError>, T: Transform<S::Service, InitError = S::InitError>,
{
store: Rc<(T, S)>,
#[pin]
state: ApplyTransformFutureState<T, S>,
}
#[pin_project::pin_project(project = ApplyTransformFutureStateProj)]
pub enum ApplyTransformFutureState<T, S>
where
S: ServiceFactory,
T: Transform<S::Service, InitError = S::InitError>,
{
A(#[pin] S::Future),
B(#[pin] T::Future),
}
impl<T, S> Future for ApplyTransformFuture<T, S>
where
S: ServiceFactory,
T: Transform<S::Service, InitError = S::InitError>,
{ {
type Output = Result<T::Transform, T::InitError>; type Output = Result<T::Transform, T::InitError>;
@@ -217,13 +232,15 @@ where
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
match this.state.as_mut().project() { match this.state.as_mut().project() {
ApplyTransformFutureStateProj::A { fut } => { ApplyTransformFutureStateProj::A(fut) => match fut.poll(cx)? {
let srv = ready!(fut.poll(cx))?; Poll::Ready(srv) => {
let fut = this.store.0.new_transform(srv); let fut = this.store.0.new_transform(srv);
this.state.set(ApplyTransformFutureState::B { fut }); this.state.set(ApplyTransformFutureState::B(fut));
self.poll(cx) self.poll(cx)
} }
ApplyTransformFutureStateProj::B { fut } => fut.poll(cx), Poll::Pending => Poll::Pending,
},
ApplyTransformFutureStateProj::B(fut) => fut.poll(cx),
} }
} }
} }

View File

@@ -1,85 +1,83 @@
use core::{ use std::future::Future;
future::Future, use std::marker::PhantomData;
marker::PhantomData, use std::pin::Pin;
pin::Pin, use std::task::{Context, Poll};
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use super::Transform; use super::Transform;
/// Transform for the [`TransformExt::map_init_err`] combinator, changing the type of a new /// Transform for the `map_init_err` combinator, changing the type of a new
/// [`Transform`]'s initialization error. /// transform's init error.
pub struct TransformMapInitErr<T, S, Req, F, E> { ///
transform: T, /// This is created by the `Transform::map_init_err` method.
mapper: F, pub struct TransformMapInitErr<T, S, F, E> {
_phantom: PhantomData<(S, Req, E)>, t: T,
f: F,
e: PhantomData<(S, E)>,
} }
impl<T, S, F, E, Req> TransformMapInitErr<T, S, Req, F, E> { impl<T, S, F, E> TransformMapInitErr<T, S, F, E> {
pub(crate) fn new(t: T, f: F) -> Self pub(crate) fn new(t: T, f: F) -> Self
where where
T: Transform<S, Req>, T: Transform<S>,
F: Fn(T::InitError) -> E, F: Fn(T::InitError) -> E,
{ {
Self { Self {
transform: t, t,
mapper: f, f,
_phantom: PhantomData, e: PhantomData,
} }
} }
} }
impl<T, S, Req, F, E> Clone for TransformMapInitErr<T, S, Req, F, E> impl<T, S, F, E> Clone for TransformMapInitErr<T, S, F, E>
where where
T: Clone, T: Clone,
F: Clone, F: Clone,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
transform: self.transform.clone(), t: self.t.clone(),
mapper: self.mapper.clone(), f: self.f.clone(),
_phantom: PhantomData, e: PhantomData,
} }
} }
} }
impl<T, S, F, E, Req> Transform<S, Req> for TransformMapInitErr<T, S, Req, F, E> impl<T, S, F, E> Transform<S> for TransformMapInitErr<T, S, F, E>
where where
T: Transform<S, Req>, T: Transform<S>,
F: Fn(T::InitError) -> E + Clone, F: Fn(T::InitError) -> E + Clone,
{ {
type Request = T::Request;
type Response = T::Response; type Response = T::Response;
type Error = T::Error; type Error = T::Error;
type Transform = T::Transform; type Transform = T::Transform;
type InitError = E; type InitError = E;
type Future = TransformMapInitErrFuture<T, S, F, E, Req>; type Future = TransformMapInitErrFuture<T, S, F, E>;
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
TransformMapInitErrFuture { TransformMapInitErrFuture {
fut: self.transform.new_transform(service), fut: self.t.new_transform(service),
f: self.mapper.clone(), f: self.f.clone(),
} }
} }
} }
pin_project! { #[pin_project::pin_project]
pub struct TransformMapInitErrFuture<T, S, F, E, Req> pub struct TransformMapInitErrFuture<T, S, F, E>
where where
T: Transform<S, Req>, T: Transform<S>,
F: Fn(T::InitError) -> E, F: Fn(T::InitError) -> E,
{ {
#[pin] #[pin]
fut: T::Future, fut: T::Future,
f: F, f: F,
}
} }
impl<T, S, F, E, Req> Future for TransformMapInitErrFuture<T, S, F, E, Req> impl<T, S, F, E> Future for TransformMapInitErrFuture<T, S, F, E>
where where
T: Transform<S, Req>, T: Transform<S>,
F: Fn(T::InitError) -> E + Clone, F: Fn(T::InitError) -> E + Clone,
{ {
type Output = Result<T::Transform, E>; type Output = Result<T::Transform, E>;

33
actix-testing/CHANGES.md Normal file
View File

@@ -0,0 +1,33 @@
# Changes
## [1.0.1] - 2020-05-19
* Replace deprecated `net2` crate with `socket2`
* Remove unused `futures` dependency
## [1.0.0] - 2019-12-11
* Update actix-server to 1.0.0
## [1.0.0-alpha.3] - 2019-12-07
* Migrate to tokio 0.2
## [1.0.0-alpha.2] - 2019-12-02
* Re-export `test` attribute macros
## [0.3.0-alpha.1] - 2019-11-22
* Migrate to std::future
## [0.2.0] - 2019-10-14
* Upgrade actix-server and actix-server-config deps
## [0.1.0] - 2019-09-25
* Initial impl

27
actix-testing/Cargo.toml Normal file
View File

@@ -0,0 +1,27 @@
[package]
name = "actix-testing"
version = "1.0.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix testing utils"
keywords = ["network", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-testing/"
categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0"
edition = "2018"
workspace = ".."
readme = "README.md"
[lib]
name = "actix_testing"
path = "src/lib.rs"
[dependencies]
actix-rt = "1.0.0"
actix-macros = "0.1.0"
actix-server = "1.0.0"
actix-service = "1.0.0"
log = "0.4"
socket2 = "0.3"

9
actix-testing/README.md Normal file
View File

@@ -0,0 +1,9 @@
# Actix test utilities [![crates.io](https://meritbadge.herokuapp.com/actix-testing)](https://crates.io/crates/actix-testint) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Documentation & community resources
* [User Guide](https://actix.rs/docs/)
* [API Documentation](https://docs.rs/actix-testing/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-http-test](https://crates.io/crates/actix-testing)
* Minimum supported Rust version: 1.37 or later

View File

@@ -1,9 +1,19 @@
//! Various helpers for Actix applications to use during testing.
#![deny(rust_2018_idioms, nonstandard_style)]
#![allow(clippy::type_complexity, clippy::needless_doctest_main)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
use std::sync::mpsc; use std::sync::mpsc;
use std::{net, thread}; use std::{net, thread};
use actix_rt::{net::TcpStream, System}; use actix_rt::{net::TcpStream, System};
use actix_server::{Server, ServerBuilder, ServiceFactory};
use socket2::{Domain, Protocol, Socket, Type};
use crate::{Server, ServerBuilder, ServiceFactory}; #[cfg(not(test))] // Work around for rust-lang/rust#62127
pub use actix_macros::test;
/// The `TestServer` type. /// The `TestServer` type.
/// ///
@@ -12,9 +22,9 @@ use crate::{Server, ServerBuilder, ServiceFactory};
/// ///
/// # Examples /// # Examples
/// ///
/// ``` /// ```rust
/// use actix_service::fn_service; /// use actix_service::fn_service;
/// use actix_server::TestServer; /// use actix_testing::TestServer;
/// ///
/// #[actix_rt::main] /// #[actix_rt::main]
/// async fn main() { /// async fn main() {
@@ -48,8 +58,11 @@ impl TestServer {
// run server in separate thread // run server in separate thread
thread::spawn(move || { thread::spawn(move || {
let sys = System::new(); let sys = System::new("actix-test-server");
factory(Server::build()).workers(1).disable_signals().run(); factory(Server::build())
.workers(1)
.disable_signals()
.start();
tx.send(System::current()).unwrap(); tx.send(System::current()).unwrap();
sys.run() sys.run()
@@ -70,19 +83,17 @@ impl TestServer {
// run server in separate thread // run server in separate thread
thread::spawn(move || { thread::spawn(move || {
let sys = System::new(); let sys = System::new("actix-test-server");
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
let local_addr = tcp.local_addr().unwrap(); let local_addr = tcp.local_addr().unwrap();
sys.block_on(async { Server::build()
Server::build() .listen("test", tcp, factory)?
.listen("test", tcp, factory) .workers(1)
.unwrap() .disable_signals()
.workers(1) .start();
.disable_signals()
.run(); tx.send((System::current(), local_addr)).unwrap();
tx.send((System::current(), local_addr)).unwrap();
});
sys.run() sys.run()
}); });
@@ -92,20 +103,21 @@ impl TestServer {
let port = addr.port(); let port = addr.port();
TestServerRuntime { TestServerRuntime {
system,
addr, addr,
host, host,
port, port,
system,
} }
} }
/// Get first available unused local address /// Get first available unused local address
pub fn unused_addr() -> net::SocketAddr { pub fn unused_addr() -> net::SocketAddr {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
let socket = mio::net::TcpSocket::new_v4().unwrap(); let socket =
socket.bind(addr).unwrap(); Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap();
socket.set_reuseaddr(true).unwrap(); socket.bind(&addr.into()).unwrap();
let tcp = socket.listen(1024).unwrap(); socket.set_reuse_address(true).unwrap();
let tcp = socket.into_tcp_listener();
tcp.local_addr().unwrap() tcp.local_addr().unwrap()
} }
} }

View File

@@ -0,0 +1,49 @@
# Changes
## [0.3.3] - 2020-07-14
### Changed
* Update parking_lot to 0.11
## [0.3.2] - 2020-05-20
## Added
* Implement `std::error::Error` for `BlockingError` [#120]
[#120]: https://github.com/actix/actix-net/pull/120
## [0.3.1] - 2019-12-12
### Changed
* Update parking_lot to 0.10
## [0.3.0] - 2019-12-02
### Changed
* Expect `Result` type as a function return type
## [0.2.0] - 2019-11-21
### Changed
* Migrate to `std::future`
## [0.1.2] - 2019-08-05
### Changed
* Update `derive_more` to 0.15
* Update `parking_lot` to 0.9
## [0.1.1] - 2019-06-05
* Update parking_lot
## [0.1.0] - 2019-03-28
* Move threadpool to separate crate

View File

@@ -0,0 +1,27 @@
[package]
name = "actix-threadpool"
version = "0.3.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix thread pool for sync code"
keywords = ["actix", "network", "framework", "async", "futures"]
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-threadpool/"
categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018"
workspace = ".."
[lib]
name = "actix_threadpool"
path = "src/lib.rs"
[dependencies]
derive_more = "0.99.2"
futures-channel = "0.3.1"
parking_lot = "0.11"
lazy_static = "1.3"
log = "0.4"
num_cpus = "1.10"
threadpool = "1.7"

View File

@@ -0,0 +1 @@
../LICENSE-APACHE

View File

@@ -0,0 +1 @@
../LICENSE-MIT

View File

@@ -0,0 +1,96 @@
//! Thread pool for blocking operations
#![deny(rust_2018_idioms, nonstandard_style)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use derive_more::Display;
use futures_channel::oneshot;
use parking_lot::Mutex;
use threadpool::ThreadPool;
/// Env variable for default cpu pool size.
const ENV_CPU_POOL_VAR: &str = "ACTIX_THREADPOOL";
lazy_static::lazy_static! {
pub(crate) static ref DEFAULT_POOL: Mutex<ThreadPool> = {
let num = std::env::var(ENV_CPU_POOL_VAR)
.map_err(|_| ())
.and_then(|val| {
val.parse().map_err(|_| log::warn!(
"Can not parse {} value, using default",
ENV_CPU_POOL_VAR,
))
})
.unwrap_or_else(|_| num_cpus::get() * 5);
Mutex::new(
threadpool::Builder::new()
.thread_name("actix-web".to_owned())
.num_threads(num)
.build(),
)
};
}
thread_local! {
static POOL: ThreadPool = {
DEFAULT_POOL.lock().clone()
};
}
/// Blocking operation execution error
#[derive(Debug, Display)]
pub enum BlockingError<E: fmt::Debug> {
#[display(fmt = "{:?}", _0)]
Error(E),
#[display(fmt = "Thread pool is gone")]
Canceled,
}
impl<E: fmt::Debug> std::error::Error for BlockingError<E> {}
/// Execute blocking function on a thread pool, returns future that resolves
/// to result of the function execution.
pub fn run<F, I, E>(f: F) -> CpuFuture<I, E>
where
F: FnOnce() -> Result<I, E> + Send + 'static,
I: Send + 'static,
E: Send + fmt::Debug + 'static,
{
let (tx, rx) = oneshot::channel();
POOL.with(|pool| {
pool.execute(move || {
if !tx.is_canceled() {
let _ = tx.send(f());
}
})
});
CpuFuture { rx }
}
/// Blocking operation completion future. It resolves with results
/// of blocking function execution.
pub struct CpuFuture<I, E> {
rx: oneshot::Receiver<Result<I, E>>,
}
impl<I, E: fmt::Debug> Future for CpuFuture<I, E> {
type Output = Result<I, BlockingError<E>>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let rx = Pin::new(&mut self.rx);
let res = match rx.poll(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(res) => res
.map_err(|_| BlockingError::Canceled)
.and_then(|res| res.map_err(BlockingError::Error)),
};
Poll::Ready(res)
}
}

View File

@@ -1,60 +1,6 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2020-xx-xx
## 3.0.0-beta.5 - 2021-03-29
* 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]
* Rename `accept::{nativetls => native_tls}`. [#295]
* Remove `connect::TcpConnectService` type. service caller expect a `TcpStream` should use
`connect::ConnectService` instead and call `Connection<T, TcpStream>::into_parts`. [#299]
[#295]: https://github.com/actix/actix-net/pull/295
[#296]: https://github.com/actix/actix-net/pull/296
[#297]: https://github.com/actix/actix-net/pull/297
[#299]: https://github.com/actix/actix-net/pull/299
## 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 - 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]
* 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 - 2021-xx-xx
* Depend on stable trust-dns packages. [#204]
[#204]: https://github.com/actix/actix-net/pull/204
## 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 - 2020-09-03 ## 2.0.0 - 2020-09-03

90
actix-tls/Cargo.toml Executable file → Normal file
View File

@@ -1,76 +1,64 @@
[package] [package]
name = "actix-tls" name = "actix-tls"
version = "3.0.0-beta.5" version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "TLS acceptor and connector services for Actix ecosystem" description = "TLS acceptor services for Actix ecosystem."
keywords = ["network", "tls", "ssl", "async", "transport"] keywords = ["network", "framework", "async", "tls", "ssl"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-net.git" repository = "https://github.com/actix/actix-net.git"
documentation = "https://docs.rs/actix-tls" documentation = "https://docs.rs/actix-tls/"
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["openssl", "rustls", "native-tls", "accept", "connect", "uri"] features = ["openssl", "rustls", "nativetls"]
[lib] [lib]
name = "actix_tls" name = "actix_tls"
path = "src/lib.rs" path = "src/lib.rs"
[[example]]
name = "basic"
required-features = ["rustls"]
[features] [features]
default = ["accept", "connect", "uri"] default = []
# enable acceptor services
accept = []
# enable connector services
connect = []
# use openssl impls
openssl = ["tls-openssl", "tokio-openssl"]
# use rustls impls
rustls = ["tokio-rustls", "webpki-roots"]
# use native-tls impls
native-tls = ["tokio-native-tls"]
# support http::Uri as connect address
uri = ["http"]
[dependencies]
actix-codec = "0.4.0-beta.1"
actix-rt = { version = "2.2.0", default-features = false }
actix-service = "2.0.0-beta.5"
actix-utils = "3.0.0-beta.2"
derive_more = "0.99.5"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
http = { version = "0.2.3", optional = true }
log = "0.4"
tokio-util = { version = "0.6.3", default-features = false }
# openssl # openssl
tls-openssl = { package = "openssl", version = "0.10.9", optional = true } openssl = ["open-ssl", "tokio-openssl"]
tokio-openssl = { version = "0.6", optional = true }
# rustls # rustls
tokio-rustls = { version = "0.22", optional = true } rustls = ["rust-tls", "webpki", "webpki-roots", "tokio-rustls"]
webpki-roots = { version = "0.21", optional = true }
# nativetls
nativetls = ["native-tls", "tokio-tls"]
[dependencies]
actix-service = "1.0.0"
actix-codec = "0.3.0"
actix-utils = "2.0.0"
futures-util = { version = "0.3.4", default-features = false }
# openssl
open-ssl = { package = "openssl", version = "0.10", optional = true }
tokio-openssl = { version = "0.4.0", optional = true }
# rustls
rust-tls = { package = "rustls", version = "0.18.0", optional = true }
webpki = { version = "0.21", optional = true }
webpki-roots = { version = "0.20", optional = true }
tokio-rustls = { version = "0.14.0", optional = true }
# native-tls # native-tls
tokio-native-tls = { version = "0.3", optional = true } native-tls = { version = "0.2", optional = true }
tokio-tls = { version = "0.3", optional = true }
[dev-dependencies] [dev-dependencies]
actix-rt = "2.2.0" bytes = "0.5"
actix-server = "2.0.0-beta.3"
bytes = "1"
env_logger = "0.8"
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
log = "0.4" log = "0.4"
trust-dns-resolver = "0.20.0" env_logger = "0.7"
actix-testing = "1.0.0"
[[example]] actix-server = "1"
name = "tcp-rustls" actix-rt = "1"
required-features = ["accept", "rustls"]

Some files were not shown because too many files have changed in this diff Show More