mirror of
https://github.com/fafhrd91/actix-net
synced 2025-08-15 01:00:32 +02:00
Compare commits
65 Commits
utils-v3.0
...
router-v0.
Author | SHA1 | Date | |
---|---|---|---|
|
f8f1ac94bc | ||
|
82cd5b8290 | ||
|
c65e8524b2 | ||
|
a83dfaa162 | ||
|
e4ec956001 | ||
|
95cba659ff | ||
|
5687e81d9f | ||
|
a0fe2a9b2e | ||
|
ad22a93466 | ||
|
c2d5b2398a | ||
|
5b1ff30dd9 | ||
|
e1317bb3a0 | ||
|
dcea009158 | ||
|
13c18b8a51 | ||
|
06b17d6a43 | ||
|
605ec25143 | ||
|
3824493fd3 | ||
|
3be3e11aa5 | ||
|
6a5ea0342b | ||
|
23b1f63345 | ||
|
3aa037d07d | ||
|
cf21df14f2 | ||
|
a1bf8662c9 | ||
|
6f4d2220fa | ||
|
54b22f9fce | ||
|
983abec77d | ||
|
e4d4ae21ee | ||
|
8ad5f58d38 | ||
|
613b2be51f | ||
|
b2e9640952 | ||
|
76338a5822 | ||
|
978e4f25fb | ||
|
1c4e965366 | ||
|
2435520e67 | ||
|
19468feef8 | ||
|
bd48908792 | ||
|
20c2da17ed | ||
|
fdafc1dd65 | ||
|
7749dfe46a | ||
|
aeb81ad3fd | ||
|
47fba25d67 | ||
|
7a82288066 | ||
|
4e6d88d143 | ||
|
ef206f40fb | ||
|
8e98d9168c | ||
|
3c1f57706a | ||
|
d49ecf7203 | ||
|
e0fb67f646 | ||
|
ddce2d6d12 | ||
|
0a11cf5cba | ||
|
859f45868d | ||
|
d4829b046d | ||
|
5961eb892e | ||
|
995efcf427 | ||
|
f1573931dd | ||
|
3859e91799 | ||
|
8aade720ed | ||
|
8079c50ddb | ||
|
05689b86d9 | ||
|
fd3e5fba02 | ||
|
39d1f282f7 | ||
|
d8889c63ef | ||
|
fdac52aa11 | ||
|
6d66cfb06a | ||
|
fb27ffc525 |
@@ -1,3 +1,3 @@
|
|||||||
[alias]
|
[alias]
|
||||||
chk = "hack check --workspace --all-features --tests --examples"
|
chk = "check --workspace --all-features --tests --examples --bins"
|
||||||
lint = "hack --clean-per-run clippy --workspace --tests --examples"
|
lint = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo"
|
||||||
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -127,5 +127,5 @@ jobs:
|
|||||||
|
|
||||||
- name: Clear the cargo caches
|
- name: Clear the cargo caches
|
||||||
run: |
|
run: |
|
||||||
cargo install cargo-cache --no-default-features --features ci-autoclean
|
cargo install cargo-cache --version 0.6.2 --no-default-features --features ci-autoclean
|
||||||
cargo-cache
|
cargo-cache
|
||||||
|
2
.github/workflows/clippy-fmt.yml
vendored
2
.github/workflows/clippy-fmt.yml
vendored
@@ -39,4 +39,4 @@ jobs:
|
|||||||
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 --all-features --tests --examples --bins -- -Dclippy::todo
|
||||||
|
@@ -27,3 +27,8 @@ actix-utils = { path = "actix-utils" }
|
|||||||
bytestring = { path = "bytestring" }
|
bytestring = { path = "bytestring" }
|
||||||
local-channel = { path = "local-channel" }
|
local-channel = { path = "local-channel" }
|
||||||
local-waker = { path = "local-waker" }
|
local-waker = { path = "local-waker" }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
opt-level = 3
|
||||||
|
codegen-units = 1
|
||||||
|
@@ -186,7 +186,7 @@
|
|||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2017-NOW Nikolay Kim
|
Copyright 2017-NOW Actix Team
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2017 Nikolay Kim
|
Copyright (c) 2017-NOW Actix Team
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any
|
Permission is hereby granted, free of charge, to any
|
||||||
person obtaining a copy of this software and associated
|
person obtaining a copy of this software and associated
|
||||||
|
@@ -3,6 +3,10 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.0 - 2021-04-20
|
||||||
|
* No significant changes since v0.4.0-beta.1.
|
||||||
|
|
||||||
|
|
||||||
## 0.4.0-beta.1 - 2020-12-28
|
## 0.4.0-beta.1 - 2020-12-28
|
||||||
* Replace `pin-project` with `pin-project-lite`. [#237]
|
* Replace `pin-project` with `pin-project-lite`. [#237]
|
||||||
* Upgrade `tokio` dependency to `1`. [#237]
|
* Upgrade `tokio` dependency to `1`. [#237]
|
||||||
@@ -23,28 +27,28 @@
|
|||||||
## 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`.
|
||||||
* Improve `BytesCodec` `.encode()` performance
|
* Improve `BytesCodec::encode()` performance.
|
||||||
* Simplify `BytesCodec` `.decode()`
|
* Simplify `BytesCodec::decode()`.
|
||||||
* Rename methods on `Framed` to better describe their use.
|
* Rename methods on `Framed` to better describe their use.
|
||||||
* 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
|
||||||
@@ -56,4 +60,4 @@
|
|||||||
|
|
||||||
|
|
||||||
## 0.1.0 - 2018-12-09
|
## 0.1.0 - 2018-12-09
|
||||||
* Move codec to separate crate
|
* Move codec to separate crate.
|
||||||
|
@@ -1,12 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-codec"
|
name = "actix-codec"
|
||||||
version = "0.4.0-beta.1"
|
version = "0.4.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"
|
repository = "https://github.com/actix/actix-net"
|
||||||
repository = "https://github.com/actix/actix-net.git"
|
|
||||||
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"
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
//! [`Sink`]: futures_sink::Sink
|
//! [`Sink`]: futures_sink::Sink
|
||||||
//! [`Stream`]: futures_core::Stream
|
//! [`Stream`]: futures_core::Stream
|
||||||
|
|
||||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
#![deny(rust_2018_idioms, nonstandard_style, future_incompatible)]
|
||||||
#![warn(missing_docs)]
|
#![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")]
|
||||||
|
@@ -3,6 +3,12 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.2.1 - 2021-02-02
|
||||||
|
* Add optional argument `system` to `main` macro which can be used to specify the path to `actix_rt::System` (useful for re-exports). [#363]
|
||||||
|
|
||||||
|
[#363]: https://github.com/actix/actix-net/pull/363
|
||||||
|
|
||||||
|
|
||||||
## 0.2.0 - 2021-02-02
|
## 0.2.0 - 2021-02-02
|
||||||
* Update to latest `actix_rt::System::new` signature. [#261]
|
* Update to latest `actix_rt::System::new` signature. [#261]
|
||||||
|
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-macros"
|
name = "actix-macros"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = [
|
||||||
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
|
"Ibraheem Ahmed <ibrah1440@gmail.com>",
|
||||||
|
]
|
||||||
description = "Macros for Actix system and runtime"
|
description = "Macros for Actix system and runtime"
|
||||||
repository = "https://github.com/actix/actix-net.git"
|
repository = "https://github.com/actix/actix-net"
|
||||||
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"
|
||||||
|
@@ -27,8 +27,10 @@ use quote::quote;
|
|||||||
#[allow(clippy::needless_doctest_main)]
|
#[allow(clippy::needless_doctest_main)]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
#[cfg(not(test))] // Work around for rust-lang/rust#62127
|
||||||
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
|
let mut input = syn::parse_macro_input!(item as syn::ItemFn);
|
||||||
|
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
|
||||||
|
|
||||||
let attrs = &input.attrs;
|
let attrs = &input.attrs;
|
||||||
let vis = &input.vis;
|
let vis = &input.vis;
|
||||||
let sig = &mut input.sig;
|
let sig = &mut input.sig;
|
||||||
@@ -43,13 +45,47 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut system = syn::parse_str::<syn::Path>("::actix_rt::System").unwrap();
|
||||||
|
|
||||||
|
for arg in &args {
|
||||||
|
match arg {
|
||||||
|
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
|
||||||
|
lit: syn::Lit::Str(lit),
|
||||||
|
path,
|
||||||
|
..
|
||||||
|
})) => match path
|
||||||
|
.get_ident()
|
||||||
|
.map(|i| i.to_string().to_lowercase())
|
||||||
|
.as_deref()
|
||||||
|
{
|
||||||
|
Some("system") => match lit.parse() {
|
||||||
|
Ok(path) => system = path,
|
||||||
|
Err(_) => {
|
||||||
|
return syn::Error::new_spanned(lit, "Expected path")
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return syn::Error::new_spanned(arg, "Unknown attribute specified")
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sig.asyncness = None;
|
sig.asyncness = None;
|
||||||
|
|
||||||
(quote! {
|
(quote! {
|
||||||
#(#attrs)*
|
#(#attrs)*
|
||||||
#vis #sig {
|
#vis #sig {
|
||||||
actix_rt::System::new()
|
<#system>::new().block_on(async move { #body })
|
||||||
.block_on(async move { #body })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
|
@@ -4,6 +4,9 @@ fn compile_macros() {
|
|||||||
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/main-03-fn-params.rs");
|
||||||
|
t.pass("tests/trybuild/main-04-system-path.rs");
|
||||||
|
t.compile_fail("tests/trybuild/main-05-system-expect-path.rs");
|
||||||
|
t.compile_fail("tests/trybuild/main-06-unknown-attr.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");
|
||||||
|
8
actix-macros/tests/trybuild/main-04-system-path.rs
Normal file
8
actix-macros/tests/trybuild/main-04-system-path.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
mod system {
|
||||||
|
pub use actix_rt::System as MySystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::main(system = "system::MySystem")]
|
||||||
|
async fn main() {
|
||||||
|
futures_util::future::ready(()).await
|
||||||
|
}
|
@@ -0,0 +1,4 @@
|
|||||||
|
#[actix_rt::main(system = "!@#*&")]
|
||||||
|
async fn main2() {}
|
||||||
|
|
||||||
|
fn main() {}
|
@@ -0,0 +1,5 @@
|
|||||||
|
error: Expected path
|
||||||
|
--> $DIR/main-05-system-expect-path.rs:1:27
|
||||||
|
|
|
||||||
|
1 | #[actix_rt::main(system = "!@#*&")]
|
||||||
|
| ^^^^^^^
|
7
actix-macros/tests/trybuild/main-06-unknown-attr.rs
Normal file
7
actix-macros/tests/trybuild/main-06-unknown-attr.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#[actix_rt::main(foo = "bar")]
|
||||||
|
async fn async_main() {}
|
||||||
|
|
||||||
|
#[actix_rt::main(bar::baz)]
|
||||||
|
async fn async_main2() {}
|
||||||
|
|
||||||
|
fn main() {}
|
11
actix-macros/tests/trybuild/main-06-unknown-attr.stderr
Normal file
11
actix-macros/tests/trybuild/main-06-unknown-attr.stderr
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
error: Unknown attribute specified
|
||||||
|
--> $DIR/main-06-unknown-attr.rs:1:18
|
||||||
|
|
|
||||||
|
1 | #[actix_rt::main(foo = "bar")]
|
||||||
|
| ^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: Unknown attribute specified
|
||||||
|
--> $DIR/main-06-unknown-attr.rs:4:18
|
||||||
|
|
|
||||||
|
4 | #[actix_rt::main(bar::baz)]
|
||||||
|
| ^^^^^^^^
|
@@ -3,6 +3,50 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.0-beta.1 - 2021-07-20
|
||||||
|
* Fix a bug in multi-patterns where static patterns are interpreted as regex. [#366]
|
||||||
|
* Introduce `ResourceDef::pattern_iter` to get an iterator over all patterns in a multi-pattern resource. [#373]
|
||||||
|
* Fix segment interpolation leaving `Path` in unintended state after matching. [#368]
|
||||||
|
* Fix `ResourceDef` `PartialEq` implementation. [#373]
|
||||||
|
* Re-work `IntoPatterns` trait, adding a `Patterns` enum. [#372]
|
||||||
|
* Implement `IntoPatterns` for `bytestring::ByteString`. [#372]
|
||||||
|
* Rename `Path::{len => segment_count}` to be more descriptive of it's purpose. [#370]
|
||||||
|
* Rename `ResourceDef::{resource_path => resource_path_from_iter}`. [#371]
|
||||||
|
* `ResourceDef::resource_path_from_iter` now takes an `IntoIterator`. [#373]
|
||||||
|
* Rename `ResourceDef::{resource_path_named => resource_path_from_map}`. [#371]
|
||||||
|
* Rename `ResourceDef::{is_prefix_match => find_match}`. [#373]
|
||||||
|
* Rename `ResourceDef::{match_path => capture_match_info}`. [#373]
|
||||||
|
* Rename `ResourceDef::{match_path_checked => capture_match_info_fn}`. [#373]
|
||||||
|
* Remove `ResourceDef::name_mut` and introduce `ResourceDef::set_name`. [#373]
|
||||||
|
* Rename `Router::{*_checked => *_fn}`. [#373]
|
||||||
|
* Return type of `ResourceDef::name` is now `Option<&str>`. [#373]
|
||||||
|
* Return type of `ResourceDef::pattern` is now `Option<&str>`. [#373]
|
||||||
|
|
||||||
|
[#368]: https://github.com/actix/actix-net/pull/368
|
||||||
|
[#366]: https://github.com/actix/actix-net/pull/366
|
||||||
|
[#368]: https://github.com/actix/actix-net/pull/368
|
||||||
|
[#370]: https://github.com/actix/actix-net/pull/370
|
||||||
|
[#371]: https://github.com/actix/actix-net/pull/371
|
||||||
|
[#372]: https://github.com/actix/actix-net/pull/372
|
||||||
|
[#373]: https://github.com/actix/actix-net/pull/373
|
||||||
|
|
||||||
|
|
||||||
|
## 0.4.0 - 2021-06-06
|
||||||
|
* When matching path parameters, `%25` is now kept in the percent-encoded form; no longer decoded to `%`. [#357]
|
||||||
|
* Path tail patterns now match new lines (`\n`) in request URL. [#360]
|
||||||
|
* Fixed a safety bug where `Path` could return a malformed string after percent decoding. [#359]
|
||||||
|
* Methods `Path::{add, add_static}` now take `impl Into<Cow<'static, str>>`. [#345]
|
||||||
|
|
||||||
|
[#345]: https://github.com/actix/actix-net/pull/345
|
||||||
|
[#357]: https://github.com/actix/actix-net/pull/357
|
||||||
|
[#359]: https://github.com/actix/actix-net/pull/359
|
||||||
|
[#360]: https://github.com/actix/actix-net/pull/360
|
||||||
|
|
||||||
|
|
||||||
|
## 0.3.0 - 2019-12-31
|
||||||
|
* Version was yanked previously. See https://crates.io/crates/actix-router/0.3.0
|
||||||
|
|
||||||
|
|
||||||
## 0.2.7 - 2021-02-06
|
## 0.2.7 - 2021-02-06
|
||||||
* Add `Router::recognize_checked` [#247]
|
* Add `Router::recognize_checked` [#247]
|
||||||
|
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.2.7"
|
version = "0.5.0-beta.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = [
|
||||||
description = "Resource path matching library"
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
|
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||||
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
|
]
|
||||||
|
description = "Resource path matching and router"
|
||||||
keywords = ["actix", "router", "routing"]
|
keywords = ["actix", "router", "routing"]
|
||||||
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-router"
|
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@@ -18,12 +20,19 @@ path = "src/lib.rs"
|
|||||||
default = ["http"]
|
default = ["http"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
regex = "1.3.1"
|
|
||||||
serde = "1.0.104"
|
|
||||||
bytestring = ">=0.1.5, <2"
|
bytestring = ">=0.1.5, <2"
|
||||||
log = "0.4.8"
|
firestorm = "0.4"
|
||||||
http = { version = "0.2.2", optional = true }
|
http = { version = "0.2.3", optional = true }
|
||||||
|
log = "0.4"
|
||||||
|
regex = "1.5"
|
||||||
|
serde = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
http = "0.2.2"
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
serde_derive = "1.0"
|
firestorm = { version = "0.4", features = ["enable_system_time"] }
|
||||||
|
http = "0.2.3"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "router"
|
||||||
|
harness = false
|
||||||
|
194
actix-router/benches/router.rs
Normal file
194
actix-router/benches/router.rs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
//! Based on https://github.com/ibraheemdev/matchit/blob/master/benches/bench.rs
|
||||||
|
|
||||||
|
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||||
|
|
||||||
|
macro_rules! register {
|
||||||
|
(colon) => {{
|
||||||
|
register!(finish => ":p1", ":p2", ":p3", ":p4")
|
||||||
|
}};
|
||||||
|
(brackets) => {{
|
||||||
|
register!(finish => "{p1}", "{p2}", "{p3}", "{p4}")
|
||||||
|
}};
|
||||||
|
(regex) => {{
|
||||||
|
register!(finish => "(.*)", "(.*)", "(.*)", "(.*)")
|
||||||
|
}};
|
||||||
|
(finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
|
||||||
|
let arr = [
|
||||||
|
concat!("/authorizations"),
|
||||||
|
concat!("/authorizations/", $p1),
|
||||||
|
concat!("/applications/", $p1, "/tokens/", $p2),
|
||||||
|
concat!("/events"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/events"),
|
||||||
|
concat!("/networks/", $p1, "/", $p2, "/events"),
|
||||||
|
concat!("/orgs/", $p1, "/events"),
|
||||||
|
concat!("/users/", $p1, "/received_events"),
|
||||||
|
concat!("/users/", $p1, "/received_events/public"),
|
||||||
|
concat!("/users/", $p1, "/events"),
|
||||||
|
concat!("/users/", $p1, "/events/public"),
|
||||||
|
concat!("/users/", $p1, "/events/orgs/", $p2),
|
||||||
|
concat!("/feeds"),
|
||||||
|
concat!("/notifications"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/notifications"),
|
||||||
|
concat!("/notifications/threads/", $p1),
|
||||||
|
concat!("/notifications/threads/", $p1, "/subscription"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/stargazers"),
|
||||||
|
concat!("/users/", $p1, "/starred"),
|
||||||
|
concat!("/user/starred"),
|
||||||
|
concat!("/user/starred/", $p1, "/", $p2),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/subscribers"),
|
||||||
|
concat!("/users/", $p1, "/subscriptions"),
|
||||||
|
concat!("/user/subscriptions"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/subscription"),
|
||||||
|
concat!("/user/subscriptions/", $p1, "/", $p2),
|
||||||
|
concat!("/users/", $p1, "/gists"),
|
||||||
|
concat!("/gists"),
|
||||||
|
concat!("/gists/", $p1),
|
||||||
|
concat!("/gists/", $p1, "/star"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/git/blobs/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/git/commits/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/git/refs"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/git/tags/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/git/trees/", $p3),
|
||||||
|
concat!("/issues"),
|
||||||
|
concat!("/user/issues"),
|
||||||
|
concat!("/orgs/", $p1, "/issues"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/issues"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/assignees"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/assignees/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/comments"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/events"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/labels"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/labels/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/labels"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3, "/labels"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/milestones/"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3),
|
||||||
|
concat!("/emojis"),
|
||||||
|
concat!("/gitignore/templates"),
|
||||||
|
concat!("/gitignore/templates/", $p1),
|
||||||
|
concat!("/meta"),
|
||||||
|
concat!("/rate_limit"),
|
||||||
|
concat!("/users/", $p1, "/orgs"),
|
||||||
|
concat!("/user/orgs"),
|
||||||
|
concat!("/orgs/", $p1),
|
||||||
|
concat!("/orgs/", $p1, "/members"),
|
||||||
|
concat!("/orgs/", $p1, "/members", $p2),
|
||||||
|
concat!("/orgs/", $p1, "/public_members"),
|
||||||
|
concat!("/orgs/", $p1, "/public_members/", $p2),
|
||||||
|
concat!("/orgs/", $p1, "/teams"),
|
||||||
|
concat!("/teams/", $p1),
|
||||||
|
concat!("/teams/", $p1, "/members"),
|
||||||
|
concat!("/teams/", $p1, "/members", $p2),
|
||||||
|
concat!("/teams/", $p1, "/repos"),
|
||||||
|
concat!("/teams/", $p1, "/repos/", $p2, "/", $p3),
|
||||||
|
concat!("/user/teams"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/pulls"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/commits"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/files"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/merge"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/comments"),
|
||||||
|
concat!("/user/repos"),
|
||||||
|
concat!("/users/", $p1, "/repos"),
|
||||||
|
concat!("/orgs/", $p1, "/repos"),
|
||||||
|
concat!("/repositories"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/contributors"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/languages"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/teams"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/tags"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/branches"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/branches/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/collaborators"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/collaborators/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/comments"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3, "/comments"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/commits"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/readme"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/keys"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/keys", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/downloads"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/downloads", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/forks"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/hooks"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/hooks", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/releases"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3, "/assets"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/stats/contributors"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/stats/commit_activity"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/stats/code_frequency"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/stats/participation"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/stats/punch_card"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/statuses/", $p3),
|
||||||
|
concat!("/search/repositories"),
|
||||||
|
concat!("/search/code"),
|
||||||
|
concat!("/search/issues"),
|
||||||
|
concat!("/search/users"),
|
||||||
|
concat!("/legacy/issues/search/", $p1, "/", $p2, "/", $p3, "/", $p4),
|
||||||
|
concat!("/legacy/repos/search/", $p1),
|
||||||
|
concat!("/legacy/user/search/", $p1),
|
||||||
|
concat!("/legacy/user/email/", $p1),
|
||||||
|
concat!("/users/", $p1),
|
||||||
|
concat!("/user"),
|
||||||
|
concat!("/users"),
|
||||||
|
concat!("/user/emails"),
|
||||||
|
concat!("/users/", $p1, "/followers"),
|
||||||
|
concat!("/user/followers"),
|
||||||
|
concat!("/users/", $p1, "/following"),
|
||||||
|
concat!("/user/following"),
|
||||||
|
concat!("/user/following/", $p1),
|
||||||
|
concat!("/users/", $p1, "/following", $p2),
|
||||||
|
concat!("/users/", $p1, "/keys"),
|
||||||
|
concat!("/user/keys"),
|
||||||
|
concat!("/user/keys/", $p1),
|
||||||
|
];
|
||||||
|
std::array::IntoIter::new(arr)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call() -> impl Iterator<Item = &'static str> {
|
||||||
|
let arr = [
|
||||||
|
"/authorizations",
|
||||||
|
"/user/repos",
|
||||||
|
"/repos/rust-lang/rust/stargazers",
|
||||||
|
"/orgs/rust-lang/public_members/nikomatsakis",
|
||||||
|
"/repos/rust-lang/rust/releases/1.51.0",
|
||||||
|
];
|
||||||
|
|
||||||
|
std::array::IntoIter::new(arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_routers(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("Compare Routers");
|
||||||
|
|
||||||
|
let mut actix = actix_router::Router::<bool>::build();
|
||||||
|
for route in register!(brackets) {
|
||||||
|
actix.path(route, true);
|
||||||
|
}
|
||||||
|
let actix = actix.finish();
|
||||||
|
group.bench_function("actix", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
for route in call() {
|
||||||
|
let mut path = actix_router::Path::new(route);
|
||||||
|
black_box(actix.recognize(&mut path).unwrap());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let regex_set = regex::RegexSet::new(register!(regex)).unwrap();
|
||||||
|
group.bench_function("regex", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
for route in call() {
|
||||||
|
black_box(regex_set.matches(route));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, compare_routers);
|
||||||
|
criterion_main!(benches);
|
169
actix-router/examples/flamegraph.rs
Normal file
169
actix-router/examples/flamegraph.rs
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
macro_rules! register {
|
||||||
|
(brackets) => {{
|
||||||
|
register!(finish => "{p1}", "{p2}", "{p3}", "{p4}")
|
||||||
|
}};
|
||||||
|
(finish => $p1:literal, $p2:literal, $p3:literal, $p4:literal) => {{
|
||||||
|
let arr = [
|
||||||
|
concat!("/authorizations"),
|
||||||
|
concat!("/authorizations/", $p1),
|
||||||
|
concat!("/applications/", $p1, "/tokens/", $p2),
|
||||||
|
concat!("/events"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/events"),
|
||||||
|
concat!("/networks/", $p1, "/", $p2, "/events"),
|
||||||
|
concat!("/orgs/", $p1, "/events"),
|
||||||
|
concat!("/users/", $p1, "/received_events"),
|
||||||
|
concat!("/users/", $p1, "/received_events/public"),
|
||||||
|
concat!("/users/", $p1, "/events"),
|
||||||
|
concat!("/users/", $p1, "/events/public"),
|
||||||
|
concat!("/users/", $p1, "/events/orgs/", $p2),
|
||||||
|
concat!("/feeds"),
|
||||||
|
concat!("/notifications"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/notifications"),
|
||||||
|
concat!("/notifications/threads/", $p1),
|
||||||
|
concat!("/notifications/threads/", $p1, "/subscription"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/stargazers"),
|
||||||
|
concat!("/users/", $p1, "/starred"),
|
||||||
|
concat!("/user/starred"),
|
||||||
|
concat!("/user/starred/", $p1, "/", $p2),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/subscribers"),
|
||||||
|
concat!("/users/", $p1, "/subscriptions"),
|
||||||
|
concat!("/user/subscriptions"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/subscription"),
|
||||||
|
concat!("/user/subscriptions/", $p1, "/", $p2),
|
||||||
|
concat!("/users/", $p1, "/gists"),
|
||||||
|
concat!("/gists"),
|
||||||
|
concat!("/gists/", $p1),
|
||||||
|
concat!("/gists/", $p1, "/star"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/git/blobs/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/git/commits/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/git/refs"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/git/tags/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/git/trees/", $p3),
|
||||||
|
concat!("/issues"),
|
||||||
|
concat!("/user/issues"),
|
||||||
|
concat!("/orgs/", $p1, "/issues"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/issues"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/assignees"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/assignees/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/comments"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/events"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/labels"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/labels/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/issues/", $p3, "/labels"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3, "/labels"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/milestones/"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/milestones/", $p3),
|
||||||
|
concat!("/emojis"),
|
||||||
|
concat!("/gitignore/templates"),
|
||||||
|
concat!("/gitignore/templates/", $p1),
|
||||||
|
concat!("/meta"),
|
||||||
|
concat!("/rate_limit"),
|
||||||
|
concat!("/users/", $p1, "/orgs"),
|
||||||
|
concat!("/user/orgs"),
|
||||||
|
concat!("/orgs/", $p1),
|
||||||
|
concat!("/orgs/", $p1, "/members"),
|
||||||
|
concat!("/orgs/", $p1, "/members", $p2),
|
||||||
|
concat!("/orgs/", $p1, "/public_members"),
|
||||||
|
concat!("/orgs/", $p1, "/public_members/", $p2),
|
||||||
|
concat!("/orgs/", $p1, "/teams"),
|
||||||
|
concat!("/teams/", $p1),
|
||||||
|
concat!("/teams/", $p1, "/members"),
|
||||||
|
concat!("/teams/", $p1, "/members", $p2),
|
||||||
|
concat!("/teams/", $p1, "/repos"),
|
||||||
|
concat!("/teams/", $p1, "/repos/", $p2, "/", $p3),
|
||||||
|
concat!("/user/teams"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/pulls"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/commits"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/files"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/merge"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/pulls/", $p3, "/comments"),
|
||||||
|
concat!("/user/repos"),
|
||||||
|
concat!("/users/", $p1, "/repos"),
|
||||||
|
concat!("/orgs/", $p1, "/repos"),
|
||||||
|
concat!("/repositories"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/contributors"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/languages"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/teams"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/tags"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/branches"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/branches/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/collaborators"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/collaborators/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/comments"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3, "/comments"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/commits"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/commits/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/readme"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/keys"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/keys", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/downloads"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/downloads", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/forks"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/hooks"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/hooks", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/releases"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/releases/", $p3, "/assets"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/stats/contributors"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/stats/commit_activity"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/stats/code_frequency"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/stats/participation"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/stats/punch_card"),
|
||||||
|
concat!("/repos/", $p1, "/", $p2, "/statuses/", $p3),
|
||||||
|
concat!("/search/repositories"),
|
||||||
|
concat!("/search/code"),
|
||||||
|
concat!("/search/issues"),
|
||||||
|
concat!("/search/users"),
|
||||||
|
concat!("/legacy/issues/search/", $p1, "/", $p2, "/", $p3, "/", $p4),
|
||||||
|
concat!("/legacy/repos/search/", $p1),
|
||||||
|
concat!("/legacy/user/search/", $p1),
|
||||||
|
concat!("/legacy/user/email/", $p1),
|
||||||
|
concat!("/users/", $p1),
|
||||||
|
concat!("/user"),
|
||||||
|
concat!("/users"),
|
||||||
|
concat!("/user/emails"),
|
||||||
|
concat!("/users/", $p1, "/followers"),
|
||||||
|
concat!("/user/followers"),
|
||||||
|
concat!("/users/", $p1, "/following"),
|
||||||
|
concat!("/user/following"),
|
||||||
|
concat!("/user/following/", $p1),
|
||||||
|
concat!("/users/", $p1, "/following", $p2),
|
||||||
|
concat!("/users/", $p1, "/keys"),
|
||||||
|
concat!("/user/keys"),
|
||||||
|
concat!("/user/keys/", $p1),
|
||||||
|
];
|
||||||
|
|
||||||
|
arr.to_vec()
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
static PATHS: [&str; 5] = [
|
||||||
|
"/authorizations",
|
||||||
|
"/user/repos",
|
||||||
|
"/repos/rust-lang/rust/stargazers",
|
||||||
|
"/orgs/rust-lang/public_members/nikomatsakis",
|
||||||
|
"/repos/rust-lang/rust/releases/1.51.0",
|
||||||
|
];
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut router = actix_router::Router::<bool>::build();
|
||||||
|
|
||||||
|
for route in register!(brackets) {
|
||||||
|
router.path(route, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let actix = router.finish();
|
||||||
|
|
||||||
|
if firestorm::enabled() {
|
||||||
|
firestorm::bench("target", || {
|
||||||
|
for &route in &PATHS {
|
||||||
|
let mut path = actix_router::Path::new(route);
|
||||||
|
actix.recognize(&mut path).unwrap();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
@@ -24,9 +24,12 @@ macro_rules! parse_single_value {
|
|||||||
where
|
where
|
||||||
V: Visitor<'de>,
|
V: Visitor<'de>,
|
||||||
{
|
{
|
||||||
if self.path.len() != 1 {
|
if self.path.segment_count() != 1 {
|
||||||
Err(de::value::Error::custom(
|
Err(de::value::Error::custom(
|
||||||
format!("wrong number of parameters: {} expected 1", self.path.len())
|
format!(
|
||||||
|
"wrong number of parameters: {} expected 1",
|
||||||
|
self.path.segment_count()
|
||||||
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
@@ -110,11 +113,11 @@ impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T>
|
|||||||
where
|
where
|
||||||
V: Visitor<'de>,
|
V: Visitor<'de>,
|
||||||
{
|
{
|
||||||
if self.path.len() < len {
|
if self.path.segment_count() < len {
|
||||||
Err(de::value::Error::custom(
|
Err(de::value::Error::custom(
|
||||||
format!(
|
format!(
|
||||||
"wrong number of parameters: {} expected {}",
|
"wrong number of parameters: {} expected {}",
|
||||||
self.path.len(),
|
self.path.segment_count(),
|
||||||
len
|
len
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
@@ -135,11 +138,11 @@ impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T>
|
|||||||
where
|
where
|
||||||
V: Visitor<'de>,
|
V: Visitor<'de>,
|
||||||
{
|
{
|
||||||
if self.path.len() < len {
|
if self.path.segment_count() < len {
|
||||||
Err(de::value::Error::custom(
|
Err(de::value::Error::custom(
|
||||||
format!(
|
format!(
|
||||||
"wrong number of parameters: {} expected {}",
|
"wrong number of parameters: {} expected {}",
|
||||||
self.path.len(),
|
self.path.segment_count(),
|
||||||
len
|
len
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
@@ -173,9 +176,13 @@ impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T>
|
|||||||
where
|
where
|
||||||
V: Visitor<'de>,
|
V: Visitor<'de>,
|
||||||
{
|
{
|
||||||
if self.path.len() != 1 {
|
if self.path.segment_count() != 1 {
|
||||||
Err(de::value::Error::custom(
|
Err(de::value::Error::custom(
|
||||||
format!("wrong number of parameters: {} expected 1", self.path.len()).as_str(),
|
format!(
|
||||||
|
"wrong number of parameters: {} expected 1",
|
||||||
|
self.path.segment_count()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
visitor.visit_str(&self.path[0])
|
visitor.visit_str(&self.path[0])
|
||||||
@@ -485,8 +492,7 @@ impl<'de> de::VariantAccess<'de> for UnitVariant {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use serde::de;
|
use serde::{de, Deserialize};
|
||||||
use serde_derive::Deserialize;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::path::Path;
|
use crate::path::Path;
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
//! Resource path matching library.
|
//! Resource path matching and router.
|
||||||
|
|
||||||
#![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")]
|
||||||
@@ -14,6 +14,8 @@ pub use self::path::Path;
|
|||||||
pub use self::resource::ResourceDef;
|
pub use self::resource::ResourceDef;
|
||||||
pub use self::router::{ResourceInfo, Router, RouterBuilder};
|
pub use self::router::{ResourceInfo, Router, RouterBuilder};
|
||||||
|
|
||||||
|
// TODO: this trait is necessary, document it
|
||||||
|
// see impl Resource for ServiceRequest
|
||||||
pub trait Resource<T: ResourcePath> {
|
pub trait Resource<T: ResourcePath> {
|
||||||
fn resource_path(&mut self) -> &mut Path<T>;
|
fn resource_path(&mut self) -> &mut Path<T>;
|
||||||
}
|
}
|
||||||
@@ -40,98 +42,92 @@ impl ResourcePath for bytestring::ByteString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper trait for type that could be converted to path pattern
|
/// One or many patterns.
|
||||||
pub trait IntoPattern {
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
fn is_single(&self) -> bool;
|
pub enum Patterns {
|
||||||
|
Single(String),
|
||||||
fn patterns(&self) -> Vec<String>;
|
List(Vec<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoPattern for String {
|
impl Patterns {
|
||||||
fn is_single(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
true
|
match self {
|
||||||
|
Patterns::Single(_) => false,
|
||||||
|
Patterns::List(pats) => pats.is_empty(),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn patterns(&self) -> Vec<String> {
|
|
||||||
vec![self.clone()]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoPattern for &'a String {
|
/// Helper trait for type that could be converted to one or more path pattern.
|
||||||
fn is_single(&self) -> bool {
|
pub trait IntoPatterns {
|
||||||
true
|
fn patterns(&self) -> Patterns;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn patterns(&self) -> Vec<String> {
|
impl IntoPatterns for String {
|
||||||
vec![self.as_str().to_string()]
|
fn patterns(&self) -> Patterns {
|
||||||
|
Patterns::Single(self.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoPattern for &'a str {
|
impl<'a> IntoPatterns for &'a String {
|
||||||
fn is_single(&self) -> bool {
|
fn patterns(&self) -> Patterns {
|
||||||
true
|
Patterns::Single((*self).clone())
|
||||||
}
|
|
||||||
|
|
||||||
fn patterns(&self) -> Vec<String> {
|
|
||||||
vec![(*self).to_string()]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsRef<str>> IntoPattern for Vec<T> {
|
impl<'a> IntoPatterns for &'a str {
|
||||||
fn is_single(&self) -> bool {
|
fn patterns(&self) -> Patterns {
|
||||||
self.len() == 1
|
Patterns::Single((*self).to_owned())
|
||||||
}
|
|
||||||
|
|
||||||
fn patterns(&self) -> Vec<String> {
|
|
||||||
self.iter().map(|v| v.as_ref().to_string()).collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! array_patterns (($tp:ty, $num:tt) => {
|
impl IntoPatterns for bytestring::ByteString {
|
||||||
impl IntoPattern for [$tp; $num] {
|
fn patterns(&self) -> Patterns {
|
||||||
fn is_single(&self) -> bool {
|
Patterns::Single(self.to_string())
|
||||||
$num == 1
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn patterns(&self) -> Vec<String> {
|
impl IntoPatterns for Patterns {
|
||||||
self.iter().map(|v| v.to_string()).collect()
|
fn patterns(&self) -> Patterns {
|
||||||
|
self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<str>> IntoPatterns for Vec<T> {
|
||||||
|
fn patterns(&self) -> Patterns {
|
||||||
|
let mut patterns = self.iter().map(|v| v.as_ref().to_owned());
|
||||||
|
|
||||||
|
match patterns.size_hint() {
|
||||||
|
(1, _) => Patterns::Single(patterns.next().unwrap()),
|
||||||
|
_ => Patterns::List(patterns.collect()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! array_patterns_single (($tp:ty) => {
|
||||||
|
impl IntoPatterns for [$tp; 1] {
|
||||||
|
fn patterns(&self) -> Patterns {
|
||||||
|
Patterns::Single(self[0].to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
array_patterns!(&str, 1);
|
macro_rules! array_patterns_multiple (($tp:ty, $str_fn:expr, $($num:tt) +) => {
|
||||||
array_patterns!(&str, 2);
|
// for each array length specified in $num
|
||||||
array_patterns!(&str, 3);
|
$(
|
||||||
array_patterns!(&str, 4);
|
impl IntoPatterns for [$tp; $num] {
|
||||||
array_patterns!(&str, 5);
|
fn patterns(&self) -> Patterns {
|
||||||
array_patterns!(&str, 6);
|
Patterns::List(self.iter().map($str_fn).collect())
|
||||||
array_patterns!(&str, 7);
|
}
|
||||||
array_patterns!(&str, 8);
|
}
|
||||||
array_patterns!(&str, 9);
|
)+
|
||||||
array_patterns!(&str, 10);
|
});
|
||||||
array_patterns!(&str, 11);
|
|
||||||
array_patterns!(&str, 12);
|
|
||||||
array_patterns!(&str, 13);
|
|
||||||
array_patterns!(&str, 14);
|
|
||||||
array_patterns!(&str, 15);
|
|
||||||
array_patterns!(&str, 16);
|
|
||||||
|
|
||||||
array_patterns!(String, 1);
|
array_patterns_single!(&str);
|
||||||
array_patterns!(String, 2);
|
array_patterns_multiple!(&str, |&v| v.to_owned(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
|
||||||
array_patterns!(String, 3);
|
|
||||||
array_patterns!(String, 4);
|
array_patterns_single!(String);
|
||||||
array_patterns!(String, 5);
|
array_patterns_multiple!(String, |v| v.clone(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
|
||||||
array_patterns!(String, 6);
|
|
||||||
array_patterns!(String, 7);
|
|
||||||
array_patterns!(String, 8);
|
|
||||||
array_patterns!(String, 9);
|
|
||||||
array_patterns!(String, 10);
|
|
||||||
array_patterns!(String, 11);
|
|
||||||
array_patterns!(String, 12);
|
|
||||||
array_patterns!(String, 13);
|
|
||||||
array_patterns!(String, 14);
|
|
||||||
array_patterns!(String, 15);
|
|
||||||
array_patterns!(String, 16);
|
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
mod url;
|
mod url;
|
||||||
@@ -140,10 +136,11 @@ mod url;
|
|||||||
pub use self::url::{Quoter, Url};
|
pub use self::url::{Quoter, Url};
|
||||||
|
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
mod http_support {
|
mod http_impls {
|
||||||
use super::ResourcePath;
|
|
||||||
use http::Uri;
|
use http::Uri;
|
||||||
|
|
||||||
|
use super::ResourcePath;
|
||||||
|
|
||||||
impl ResourcePath for Uri {
|
impl ResourcePath for Uri {
|
||||||
fn path(&self) -> &str {
|
fn path(&self) -> &str {
|
||||||
self.path()
|
self.path()
|
||||||
|
@@ -1,44 +1,31 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
use std::ops::Index;
|
use std::ops::Index;
|
||||||
|
|
||||||
|
use firestorm::profile_method;
|
||||||
use serde::de;
|
use serde::de;
|
||||||
|
|
||||||
use crate::de::PathDeserializer;
|
use crate::{de::PathDeserializer, Resource, ResourcePath};
|
||||||
use crate::{Resource, ResourcePath};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) enum PathItem {
|
pub(crate) enum PathItem {
|
||||||
Static(&'static str),
|
Static(Cow<'static, str>),
|
||||||
Segment(u16, u16),
|
Segment(u16, u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resource path match information
|
impl Default for PathItem {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Static(Cow::Borrowed(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resource path match information.
|
||||||
///
|
///
|
||||||
/// If resource path contains variable patterns, `Path` stores them.
|
/// If resource path contains variable patterns, `Path` stores them.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Path<T> {
|
pub struct Path<T> {
|
||||||
path: T,
|
path: T,
|
||||||
pub(crate) skip: u16,
|
pub(crate) skip: u16,
|
||||||
pub(crate) segments: Vec<(&'static str, PathItem)>,
|
pub(crate) segments: Vec<(Cow<'static, str>, PathItem)>,
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Default> Default for Path<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Path {
|
|
||||||
path: T::default(),
|
|
||||||
skip: 0,
|
|
||||||
segments: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Clone> Clone for Path<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Path {
|
|
||||||
path: self.path.clone(),
|
|
||||||
skip: self.skip,
|
|
||||||
segments: self.segments.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ResourcePath> Path<T> {
|
impl<T: ResourcePath> Path<T> {
|
||||||
@@ -50,21 +37,23 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get reference to inner path instance
|
/// Get reference to inner path instance.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_ref(&self) -> &T {
|
pub fn get_ref(&self) -> &T {
|
||||||
&self.path
|
&self.path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get mutable reference to inner path instance
|
/// Get mutable reference to inner path instance.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_mut(&mut self) -> &mut T {
|
pub fn get_mut(&mut self) -> &mut T {
|
||||||
&mut self.path
|
&mut self.path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Path
|
/// Path.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn path(&self) -> &str {
|
pub fn path(&self) -> &str {
|
||||||
|
profile_method!(path);
|
||||||
|
|
||||||
let skip = self.skip as usize;
|
let skip = self.skip as usize;
|
||||||
let path = self.path.path();
|
let path = self.path.path();
|
||||||
if skip <= path.len() {
|
if skip <= path.len() {
|
||||||
@@ -74,7 +63,7 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set new path
|
/// Set new path.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set(&mut self, path: T) {
|
pub fn set(&mut self, path: T) {
|
||||||
self.skip = 0;
|
self.skip = 0;
|
||||||
@@ -82,63 +71,70 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
self.segments.clear();
|
self.segments.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset state
|
/// Reset state.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.skip = 0;
|
self.skip = 0;
|
||||||
self.segments.clear();
|
self.segments.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Skip first `n` chars in path
|
/// Skip first `n` chars in path.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn skip(&mut self, n: u16) {
|
pub fn skip(&mut self, n: u16) {
|
||||||
self.skip += n;
|
self.skip += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add(&mut self, name: &'static str, value: PathItem) {
|
pub(crate) fn add(&mut self, name: impl Into<Cow<'static, str>>, value: PathItem) {
|
||||||
|
profile_method!(add);
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
PathItem::Static(s) => self.segments.push((name, PathItem::Static(s))),
|
PathItem::Static(s) => self.segments.push((name.into(), PathItem::Static(s))),
|
||||||
PathItem::Segment(begin, end) => self
|
PathItem::Segment(begin, end) => self.segments.push((
|
||||||
.segments
|
name.into(),
|
||||||
.push((name, PathItem::Segment(self.skip + begin, self.skip + end))),
|
PathItem::Segment(self.skip + begin, self.skip + end),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn add_static(&mut self, name: &'static str, value: &'static str) {
|
pub fn add_static(
|
||||||
self.segments.push((name, PathItem::Static(value)));
|
&mut self,
|
||||||
|
name: impl Into<Cow<'static, str>>,
|
||||||
|
value: impl Into<Cow<'static, str>>,
|
||||||
|
) {
|
||||||
|
self.segments
|
||||||
|
.push((name.into(), PathItem::Static(value.into())));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if there are any matched patterns
|
/// Check if there are any matched patterns.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.segments.is_empty()
|
self.segments.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check number of extracted parameters
|
/// Returns number of interpolated segments.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn len(&self) -> usize {
|
pub fn segment_count(&self) -> usize {
|
||||||
self.segments.len()
|
self.segments.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get matched parameter by name without type conversion
|
/// Get matched parameter by name without type conversion
|
||||||
pub fn get(&self, key: &str) -> Option<&str> {
|
pub fn get(&self, name: &str) -> Option<&str> {
|
||||||
for item in self.segments.iter() {
|
profile_method!(get);
|
||||||
if key == item.0 {
|
|
||||||
return match item.1 {
|
for (seg_name, val) in self.segments.iter() {
|
||||||
|
if name == seg_name {
|
||||||
|
return match val {
|
||||||
PathItem::Static(ref s) => Some(&s),
|
PathItem::Static(ref s) => Some(&s),
|
||||||
PathItem::Segment(s, e) => {
|
PathItem::Segment(s, e) => {
|
||||||
Some(&self.path.path()[(s as usize)..(e as usize)])
|
Some(&self.path.path()[(*s as usize)..(*e as usize)])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if key == "tail" {
|
|
||||||
Some(&self.path.path()[(self.skip as usize)..])
|
|
||||||
} else {
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Get unprocessed part of the path
|
/// Get unprocessed part of the path
|
||||||
pub fn unprocessed(&self) -> &str {
|
pub fn unprocessed(&self) -> &str {
|
||||||
@@ -147,9 +143,10 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
|
|
||||||
/// Get matched parameter by name.
|
/// Get matched parameter by name.
|
||||||
///
|
///
|
||||||
/// If keyed parameter is not available empty string is used as default
|
/// If keyed parameter is not available empty string is used as default value.
|
||||||
/// value.
|
|
||||||
pub fn query(&self, key: &str) -> &str {
|
pub fn query(&self, key: &str) -> &str {
|
||||||
|
profile_method!(query);
|
||||||
|
|
||||||
if let Some(s) = self.get(key) {
|
if let Some(s) = self.get(key) {
|
||||||
s
|
s
|
||||||
} else {
|
} else {
|
||||||
@@ -157,7 +154,7 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return iterator to items in parameter container
|
/// Return iterator to items in parameter container.
|
||||||
pub fn iter(&self) -> PathIter<'_, T> {
|
pub fn iter(&self) -> PathIter<'_, T> {
|
||||||
PathIter {
|
PathIter {
|
||||||
idx: 0,
|
idx: 0,
|
||||||
@@ -167,6 +164,7 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
|
|
||||||
/// Try to deserialize matching parameters to a specified type `U`
|
/// Try to deserialize matching parameters to a specified type `U`
|
||||||
pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result<U, de::value::Error> {
|
pub fn load<'de, U: serde::Deserialize<'de>>(&'de self) -> Result<U, de::value::Error> {
|
||||||
|
profile_method!(load);
|
||||||
de::Deserialize::deserialize(PathDeserializer::new(self))
|
de::Deserialize::deserialize(PathDeserializer::new(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,7 +180,7 @@ impl<'a, T: ResourcePath> Iterator for PathIter<'a, T> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn next(&mut self) -> Option<(&'a str, &'a str)> {
|
fn next(&mut self) -> Option<(&'a str, &'a str)> {
|
||||||
if self.idx < self.params.len() {
|
if self.idx < self.params.segment_count() {
|
||||||
let idx = self.idx;
|
let idx = self.idx;
|
||||||
let res = match self.params.segments[idx].1 {
|
let res = match self.params.segments[idx].1 {
|
||||||
PathItem::Static(ref s) => &s,
|
PathItem::Static(ref s) => &s,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,6 @@
|
|||||||
use crate::{IntoPattern, Resource, ResourceDef, ResourcePath};
|
use firestorm::profile_method;
|
||||||
|
|
||||||
|
use crate::{IntoPatterns, Resource, ResourceDef, ResourcePath};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct ResourceId(pub u16);
|
pub struct ResourceId(pub u16);
|
||||||
@@ -10,7 +12,11 @@ pub struct ResourceInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resource router.
|
/// Resource router.
|
||||||
pub struct Router<T, U = ()>(Vec<(ResourceDef, T, Option<U>)>);
|
// T is the resource itself
|
||||||
|
// U is any other data needed for routing like method guards
|
||||||
|
pub struct Router<T, U = ()> {
|
||||||
|
routes: Vec<(ResourceDef, T, Option<U>)>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<T, U> Router<T, U> {
|
impl<T, U> Router<T, U> {
|
||||||
pub fn build() -> RouterBuilder<T, U> {
|
pub fn build() -> RouterBuilder<T, U> {
|
||||||
@@ -24,11 +30,14 @@ impl<T, U> Router<T, U> {
|
|||||||
R: Resource<P>,
|
R: Resource<P>,
|
||||||
P: ResourcePath,
|
P: ResourcePath,
|
||||||
{
|
{
|
||||||
for item in self.0.iter() {
|
profile_method!(recognize);
|
||||||
if item.0.match_path(resource.resource_path()) {
|
|
||||||
|
for item in self.routes.iter() {
|
||||||
|
if item.0.capture_match_info(resource.resource_path()) {
|
||||||
return Some((&item.1, ResourceId(item.0.id())));
|
return Some((&item.1, ResourceId(item.0.id())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,33 +46,35 @@ impl<T, U> Router<T, U> {
|
|||||||
R: Resource<P>,
|
R: Resource<P>,
|
||||||
P: ResourcePath,
|
P: ResourcePath,
|
||||||
{
|
{
|
||||||
for item in self.0.iter_mut() {
|
profile_method!(recognize_mut);
|
||||||
if item.0.match_path(resource.resource_path()) {
|
|
||||||
|
for item in self.routes.iter_mut() {
|
||||||
|
if item.0.capture_match_info(resource.resource_path()) {
|
||||||
return Some((&mut item.1, ResourceId(item.0.id())));
|
return Some((&mut item.1, ResourceId(item.0.id())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recognize_checked<R, P, F>(
|
pub fn recognize_fn<R, P, F>(&self, resource: &mut R, check: F) -> Option<(&T, ResourceId)>
|
||||||
&self,
|
|
||||||
resource: &mut R,
|
|
||||||
check: F,
|
|
||||||
) -> Option<(&T, ResourceId)>
|
|
||||||
where
|
where
|
||||||
F: Fn(&R, &Option<U>) -> bool,
|
F: Fn(&R, &Option<U>) -> bool,
|
||||||
R: Resource<P>,
|
R: Resource<P>,
|
||||||
P: ResourcePath,
|
P: ResourcePath,
|
||||||
{
|
{
|
||||||
for item in self.0.iter() {
|
profile_method!(recognize_checked);
|
||||||
if item.0.match_path_checked(resource, &check, &item.2) {
|
|
||||||
|
for item in self.routes.iter() {
|
||||||
|
if item.0.capture_match_info_fn(resource, &check, &item.2) {
|
||||||
return Some((&item.1, ResourceId(item.0.id())));
|
return Some((&item.1, ResourceId(item.0.id())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recognize_mut_checked<R, P, F>(
|
pub fn recognize_mut_fn<R, P, F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
resource: &mut R,
|
resource: &mut R,
|
||||||
check: F,
|
check: F,
|
||||||
@@ -73,11 +84,14 @@ impl<T, U> Router<T, U> {
|
|||||||
R: Resource<P>,
|
R: Resource<P>,
|
||||||
P: ResourcePath,
|
P: ResourcePath,
|
||||||
{
|
{
|
||||||
for item in self.0.iter_mut() {
|
profile_method!(recognize_mut_checked);
|
||||||
if item.0.match_path_checked(resource, &check, &item.2) {
|
|
||||||
|
for item in self.routes.iter_mut() {
|
||||||
|
if item.0.capture_match_info_fn(resource, &check, &item.2) {
|
||||||
return Some((&mut item.1, ResourceId(item.0.id())));
|
return Some((&mut item.1, ResourceId(item.0.id())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,11 +102,13 @@ pub struct RouterBuilder<T, U = ()> {
|
|||||||
|
|
||||||
impl<T, U> RouterBuilder<T, U> {
|
impl<T, U> RouterBuilder<T, U> {
|
||||||
/// Register resource for specified path.
|
/// Register resource for specified path.
|
||||||
pub fn path<P: IntoPattern>(
|
pub fn path<P: IntoPatterns>(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: P,
|
path: P,
|
||||||
resource: T,
|
resource: T,
|
||||||
) -> &mut (ResourceDef, T, Option<U>) {
|
) -> &mut (ResourceDef, T, Option<U>) {
|
||||||
|
profile_method!(path);
|
||||||
|
|
||||||
self.resources
|
self.resources
|
||||||
.push((ResourceDef::new(path), resource, None));
|
.push((ResourceDef::new(path), resource, None));
|
||||||
self.resources.last_mut().unwrap()
|
self.resources.last_mut().unwrap()
|
||||||
@@ -100,6 +116,8 @@ impl<T, U> RouterBuilder<T, U> {
|
|||||||
|
|
||||||
/// Register resource for specified path prefix.
|
/// Register resource for specified path prefix.
|
||||||
pub fn prefix(&mut self, prefix: &str, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
pub fn prefix(&mut self, prefix: &str, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
||||||
|
profile_method!(prefix);
|
||||||
|
|
||||||
self.resources
|
self.resources
|
||||||
.push((ResourceDef::prefix(prefix), resource, None));
|
.push((ResourceDef::prefix(prefix), resource, None));
|
||||||
self.resources.last_mut().unwrap()
|
self.resources.last_mut().unwrap()
|
||||||
@@ -107,13 +125,17 @@ impl<T, U> RouterBuilder<T, U> {
|
|||||||
|
|
||||||
/// Register resource for ResourceDef
|
/// Register resource for ResourceDef
|
||||||
pub fn rdef(&mut self, rdef: ResourceDef, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
pub fn rdef(&mut self, rdef: ResourceDef, resource: T) -> &mut (ResourceDef, T, Option<U>) {
|
||||||
|
profile_method!(rdef);
|
||||||
|
|
||||||
self.resources.push((rdef, resource, None));
|
self.resources.push((rdef, resource, None));
|
||||||
self.resources.last_mut().unwrap()
|
self.resources.last_mut().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish configuration and create router instance.
|
/// Finish configuration and create router instance.
|
||||||
pub fn finish(self) -> Router<T, U> {
|
pub fn finish(self) -> Router<T, U> {
|
||||||
Router(self.resources)
|
Router {
|
||||||
|
routes: self.resources,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@ fn set_bit(array: &mut [u8], ch: u8) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"/+");
|
static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
@@ -170,11 +170,7 @@ impl Quoter {
|
|||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
cloned.map(|data| {
|
cloned.map(|data| String::from_utf8_lossy(&data).into_owned())
|
||||||
// SAFETY: we get data from http::Uri, which does UTF-8 checks already
|
|
||||||
// this code only decodes valid pct encoded values
|
|
||||||
unsafe { String::from_utf8_unchecked(data) }
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,24 +200,69 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{Path, ResourceDef};
|
use crate::{Path, ResourceDef};
|
||||||
|
|
||||||
|
const PROTECTED: &[u8] = b"%/+";
|
||||||
|
|
||||||
|
fn match_url(pattern: &'static str, url: impl AsRef<str>) -> Path<Url> {
|
||||||
|
let re = ResourceDef::new(pattern);
|
||||||
|
let uri = Uri::try_from(url.as_ref()).unwrap();
|
||||||
|
let mut path = Path::new(Url::new(uri));
|
||||||
|
assert!(re.capture_match_info(&mut path));
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn percent_encode(data: &[u8]) -> String {
|
||||||
|
data.iter().map(|c| format!("%{:02X}", c)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_url() {
|
fn test_parse_url() {
|
||||||
let re = ResourceDef::new("/user/{id}/test");
|
let re = "/user/{id}/test";
|
||||||
|
|
||||||
let url = Uri::try_from("/user/2345/test").unwrap();
|
let path = match_url(re, "/user/2345/test");
|
||||||
let mut path = Path::new(Url::new(url));
|
|
||||||
assert!(re.match_path(&mut path));
|
|
||||||
assert_eq!(path.get("id").unwrap(), "2345");
|
assert_eq!(path.get("id").unwrap(), "2345");
|
||||||
|
|
||||||
let url = Uri::try_from("/user/qwe%25/test").unwrap();
|
// "%25" should never be decoded into '%' to guarantee the output is a valid
|
||||||
let mut path = Path::new(Url::new(url));
|
// percent-encoded format
|
||||||
assert!(re.match_path(&mut path));
|
let path = match_url(re, "/user/qwe%25/test");
|
||||||
assert_eq!(path.get("id").unwrap(), "qwe%");
|
assert_eq!(path.get("id").unwrap(), "qwe%25");
|
||||||
|
|
||||||
let url = Uri::try_from("/user/qwe%25rty/test").unwrap();
|
let path = match_url(re, "/user/qwe%25rty/test");
|
||||||
let mut path = Path::new(Url::new(url));
|
assert_eq!(path.get("id").unwrap(), "qwe%25rty");
|
||||||
assert!(re.match_path(&mut path));
|
}
|
||||||
assert_eq!(path.get("id").unwrap(), "qwe%rty");
|
|
||||||
|
#[test]
|
||||||
|
fn test_protected_chars() {
|
||||||
|
let encoded = percent_encode(PROTECTED);
|
||||||
|
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
|
||||||
|
assert_eq!(path.get("id").unwrap(), &encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_non_protecteed_ascii() {
|
||||||
|
let nonprotected_ascii = ('\u{0}'..='\u{7F}')
|
||||||
|
.filter(|&c| c.is_ascii() && !PROTECTED.contains(&(c as u8)))
|
||||||
|
.collect::<String>();
|
||||||
|
let encoded = percent_encode(nonprotected_ascii.as_bytes());
|
||||||
|
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
|
||||||
|
assert_eq!(path.get("id").unwrap(), &nonprotected_ascii);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valid_utf8_multibyte() {
|
||||||
|
let test = ('\u{FF00}'..='\u{FFFF}').collect::<String>();
|
||||||
|
let encoded = percent_encode(test.as_bytes());
|
||||||
|
let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded));
|
||||||
|
assert_eq!(path.get("id").unwrap(), &test);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_utf8() {
|
||||||
|
let invalid_utf8 = percent_encode((0x80..=0xff).collect::<Vec<_>>().as_slice());
|
||||||
|
let uri = Uri::try_from(format!("/{}", invalid_utf8)).unwrap();
|
||||||
|
let path = Path::new(Url::new(uri));
|
||||||
|
|
||||||
|
// We should always get a valid utf8 string
|
||||||
|
assert!(String::from_utf8(path.path().as_bytes().to_owned()).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
`Ready` object in ok variant. [#293]
|
`Ready` object in ok variant. [#293]
|
||||||
* Breakage is acceptable since `ActixStream` was not intended to be public.
|
* Breakage is acceptable since `ActixStream` was not intended to be public.
|
||||||
|
|
||||||
[#293] https://github.com/actix/actix-net/pull/293
|
[#293]: https://github.com/actix/actix-net/pull/293
|
||||||
|
|
||||||
|
|
||||||
## 2.1.0 - 2021-02-24
|
## 2.1.0 - 2021-02-24
|
||||||
|
@@ -286,7 +286,7 @@ fn new_arbiter_with_tokio() {
|
|||||||
|
|
||||||
arb.join().unwrap();
|
arb.join().unwrap();
|
||||||
|
|
||||||
assert_eq!(false, counter.load(Ordering::SeqCst));
|
assert!(!counter.load(Ordering::SeqCst));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@@ -1,6 +1,17 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
* Remove `config` module. `ServiceConfig`, `ServiceRuntime` public types are removed due to this change. [#349]
|
||||||
|
* Remove `ServerBuilder::configure` [#349]
|
||||||
|
|
||||||
|
[#349]: https://github.com/actix/actix-net/pull/349
|
||||||
|
|
||||||
|
|
||||||
|
## 2.0.0-beta.5 - 2021-04-20
|
||||||
|
* Server shutdown would notify all workers to exit regardless if shutdown is graceful.
|
||||||
|
This would make all worker shutdown immediately in force shutdown case. [#333]
|
||||||
|
|
||||||
|
[#333]: https://github.com/actix/actix-net/pull/333
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0-beta.4 - 2021-04-01
|
## 2.0.0-beta.4 - 2021-04-01
|
||||||
|
@@ -1,14 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-server"
|
name = "actix-server"
|
||||||
version = "2.0.0-beta.4"
|
version = "2.0.0-beta.5"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"fakeshadow <24548779@qq.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"
|
repository = "https://github.com/actix/actix-net"
|
||||||
repository = "https://github.com/actix/actix-net.git"
|
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -22,14 +21,13 @@ default = []
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-rt = { version = "2.0.0", default-features = false }
|
actix-rt = { version = "2.0.0", default-features = false }
|
||||||
actix-service = "2.0.0-beta.5"
|
actix-service = "2.0.0"
|
||||||
actix-utils = "3.0.0-beta.2"
|
actix-utils = "3.0.0"
|
||||||
|
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
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"] }
|
mio = { version = "0.7.6", features = ["os-poll", "net"] }
|
||||||
num_cpus = "1.13"
|
num_cpus = "1.13"
|
||||||
slab = "0.4"
|
|
||||||
tokio = { version = "1.2", features = ["sync"] }
|
tokio = { version = "1.2", features = ["sync"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
@@ -9,15 +9,17 @@
|
|||||||
//! Start typing. When you press enter the typed line will be echoed back. The server will log
|
//! Start typing. When you press enter the typed line will be echoed back. The server will log
|
||||||
//! the length of each line it echos and the total size of data sent when the connection is closed.
|
//! the length of each line it echos and the total size of data sent when the connection is closed.
|
||||||
|
|
||||||
use std::sync::{
|
use std::{
|
||||||
|
env, io,
|
||||||
|
sync::{
|
||||||
atomic::{AtomicUsize, Ordering},
|
atomic::{AtomicUsize, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use std::{env, io};
|
|
||||||
|
|
||||||
use actix_rt::net::TcpStream;
|
use actix_rt::net::TcpStream;
|
||||||
use actix_server::Server;
|
use actix_server::Server;
|
||||||
use actix_service::pipeline_factory;
|
use actix_service::{fn_service, ServiceFactoryExt as _};
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use futures_util::future::ok;
|
use futures_util::future::ok;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
@@ -25,7 +27,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
env::set_var("RUST_LOG", "actix=trace,basic=trace");
|
env::set_var("RUST_LOG", "info");
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let count = Arc::new(AtomicUsize::new(0));
|
let count = Arc::new(AtomicUsize::new(0));
|
||||||
@@ -41,7 +43,7 @@ async fn main() -> io::Result<()> {
|
|||||||
let count = Arc::clone(&count);
|
let count = Arc::clone(&count);
|
||||||
let num2 = Arc::clone(&count);
|
let num2 = Arc::clone(&count);
|
||||||
|
|
||||||
pipeline_factory(move |mut stream: TcpStream| {
|
fn_service(move |mut stream: TcpStream| {
|
||||||
let count = Arc::clone(&count);
|
let count = Arc::clone(&count);
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
|
@@ -7,21 +7,14 @@ use actix_rt::{
|
|||||||
};
|
};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use mio::{Interest, Poll, Token as MioToken};
|
use mio::{Interest, Poll, Token as MioToken};
|
||||||
use slab::Slab;
|
|
||||||
|
|
||||||
use crate::server::Server;
|
use crate::server::Server;
|
||||||
use crate::socket::{MioListener, SocketAddr};
|
use crate::socket::MioListener;
|
||||||
use crate::waker_queue::{WakerInterest, WakerQueue, WAKER_TOKEN};
|
use crate::waker_queue::{WakerInterest, WakerQueue, WAKER_TOKEN};
|
||||||
use crate::worker::{Conn, WorkerHandle};
|
use crate::worker::{Conn, WorkerHandleAccept};
|
||||||
use crate::Token;
|
|
||||||
|
|
||||||
struct ServerSocketInfo {
|
struct ServerSocketInfo {
|
||||||
/// Address of socket. Mainly used for logging.
|
token: usize,
|
||||||
addr: SocketAddr,
|
|
||||||
|
|
||||||
/// Beware this is the crate token for identify socket and should not be confused
|
|
||||||
/// with `mio::Token`.
|
|
||||||
token: Token,
|
|
||||||
|
|
||||||
lst: MioListener,
|
lst: MioListener,
|
||||||
|
|
||||||
@@ -65,8 +58,8 @@ impl AcceptLoop {
|
|||||||
|
|
||||||
pub(crate) fn start(
|
pub(crate) fn start(
|
||||||
&mut self,
|
&mut self,
|
||||||
socks: Vec<(Token, MioListener)>,
|
socks: Vec<(usize, MioListener)>,
|
||||||
handles: Vec<WorkerHandle>,
|
handles: Vec<WorkerHandleAccept>,
|
||||||
) {
|
) {
|
||||||
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 poll = self.poll.take().unwrap();
|
||||||
@@ -80,10 +73,71 @@ impl AcceptLoop {
|
|||||||
struct Accept {
|
struct Accept {
|
||||||
poll: Poll,
|
poll: Poll,
|
||||||
waker: WakerQueue,
|
waker: WakerQueue,
|
||||||
handles: Vec<WorkerHandle>,
|
handles: Vec<WorkerHandleAccept>,
|
||||||
srv: Server,
|
srv: Server,
|
||||||
next: usize,
|
next: usize,
|
||||||
backpressure: bool,
|
avail: Availability,
|
||||||
|
paused: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Array of u128 with every bit as marker for a worker handle's availability.
|
||||||
|
struct Availability([u128; 4]);
|
||||||
|
|
||||||
|
impl Default for Availability {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self([0; 4])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Availability {
|
||||||
|
/// Check if any worker handle is available
|
||||||
|
#[inline(always)]
|
||||||
|
fn available(&self) -> bool {
|
||||||
|
self.0.iter().any(|a| *a != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if worker handle is available by index
|
||||||
|
#[inline(always)]
|
||||||
|
fn get_available(&self, idx: usize) -> bool {
|
||||||
|
let (offset, idx) = Self::offset(idx);
|
||||||
|
|
||||||
|
self.0[offset] & (1 << idx as u128) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set worker handle available state by index.
|
||||||
|
fn set_available(&mut self, idx: usize, avail: bool) {
|
||||||
|
let (offset, idx) = Self::offset(idx);
|
||||||
|
|
||||||
|
let off = 1 << idx as u128;
|
||||||
|
if avail {
|
||||||
|
self.0[offset] |= off;
|
||||||
|
} else {
|
||||||
|
self.0[offset] &= !off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set all worker handle to available state.
|
||||||
|
/// This would result in a re-check on all workers' availability.
|
||||||
|
fn set_available_all(&mut self, handles: &[WorkerHandleAccept]) {
|
||||||
|
handles.iter().for_each(|handle| {
|
||||||
|
self.set_available(handle.idx(), true);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get offset and adjusted index of given worker handle index.
|
||||||
|
fn offset(idx: usize) -> (usize, usize) {
|
||||||
|
if idx < 128 {
|
||||||
|
(0, idx)
|
||||||
|
} else if idx < 128 * 2 {
|
||||||
|
(1, idx - 128)
|
||||||
|
} else if idx < 128 * 3 {
|
||||||
|
(2, idx - 128 * 2)
|
||||||
|
} else if idx < 128 * 4 {
|
||||||
|
(3, idx - 128 * 3)
|
||||||
|
} else {
|
||||||
|
panic!("Max WorkerHandle count is 512")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function defines errors that are per-connection. Which basically
|
/// This function defines errors that are per-connection. Which basically
|
||||||
@@ -103,9 +157,9 @@ impl Accept {
|
|||||||
pub(crate) fn start(
|
pub(crate) fn start(
|
||||||
poll: Poll,
|
poll: Poll,
|
||||||
waker: WakerQueue,
|
waker: WakerQueue,
|
||||||
socks: Vec<(Token, MioListener)>,
|
socks: Vec<(usize, MioListener)>,
|
||||||
srv: Server,
|
srv: Server,
|
||||||
handles: Vec<WorkerHandle>,
|
handles: Vec<WorkerHandleAccept>,
|
||||||
) {
|
) {
|
||||||
// Accept runs in its own thread and would want to spawn additional futures to current
|
// Accept runs in its own thread and would want to spawn additional futures to current
|
||||||
// actix system.
|
// actix system.
|
||||||
@@ -114,9 +168,10 @@ impl Accept {
|
|||||||
.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, mut sockets) =
|
||||||
Accept::new_with_sockets(poll, waker, socks, handles, srv);
|
Accept::new_with_sockets(poll, waker, socks, handles, srv);
|
||||||
accept.poll_with(sockets);
|
|
||||||
|
accept.poll_with(&mut sockets);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -124,29 +179,30 @@ impl Accept {
|
|||||||
fn new_with_sockets(
|
fn new_with_sockets(
|
||||||
poll: Poll,
|
poll: Poll,
|
||||||
waker: WakerQueue,
|
waker: WakerQueue,
|
||||||
socks: Vec<(Token, MioListener)>,
|
socks: Vec<(usize, MioListener)>,
|
||||||
handles: Vec<WorkerHandle>,
|
handles: Vec<WorkerHandleAccept>,
|
||||||
srv: Server,
|
srv: Server,
|
||||||
) -> (Accept, Slab<ServerSocketInfo>) {
|
) -> (Accept, Vec<ServerSocketInfo>) {
|
||||||
let mut sockets = Slab::new();
|
let sockets = socks
|
||||||
for (hnd_token, mut lst) in socks.into_iter() {
|
.into_iter()
|
||||||
let addr = lst.local_addr();
|
.map(|(token, mut lst)| {
|
||||||
|
|
||||||
let entry = sockets.vacant_entry();
|
|
||||||
let token = entry.key();
|
|
||||||
|
|
||||||
// Start listening for incoming connections
|
// Start listening for incoming connections
|
||||||
poll.registry()
|
poll.registry()
|
||||||
.register(&mut lst, MioToken(token), Interest::READABLE)
|
.register(&mut lst, MioToken(token), Interest::READABLE)
|
||||||
.unwrap_or_else(|e| panic!("Can not register io: {}", e));
|
.unwrap_or_else(|e| panic!("Can not register io: {}", e));
|
||||||
|
|
||||||
entry.insert(ServerSocketInfo {
|
ServerSocketInfo {
|
||||||
addr,
|
token,
|
||||||
token: hnd_token,
|
|
||||||
lst,
|
lst,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut avail = Availability::default();
|
||||||
|
|
||||||
|
// Assume all handles are avail at construct time.
|
||||||
|
avail.set_available_all(&handles);
|
||||||
|
|
||||||
let accept = Accept {
|
let accept = Accept {
|
||||||
poll,
|
poll,
|
||||||
@@ -154,257 +210,264 @@ impl Accept {
|
|||||||
handles,
|
handles,
|
||||||
srv,
|
srv,
|
||||||
next: 0,
|
next: 0,
|
||||||
backpressure: false,
|
avail,
|
||||||
|
paused: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
(accept, sockets)
|
(accept, sockets)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_with(&mut self, mut sockets: Slab<ServerSocketInfo>) {
|
fn poll_with(&mut self, sockets: &mut [ServerSocketInfo]) {
|
||||||
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(e) = self.poll.poll(&mut events, None) {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
std::io::ErrorKind::Interrupted => {
|
io::ErrorKind::Interrupted => {}
|
||||||
continue;
|
_ => panic!("Poll error: {}", e),
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
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 {
|
||||||
|
WAKER_TOKEN => {
|
||||||
|
let exit = self.handle_waker(sockets);
|
||||||
|
if exit {
|
||||||
|
info!("Accept is stopped.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let token = usize::from(token);
|
||||||
|
self.accept(sockets, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_waker(&mut self, sockets: &mut [ServerSocketInfo]) -> bool {
|
||||||
// This is a loop because interests for command from previous version was
|
// This is a loop because interests for command from previous version was
|
||||||
// a loop that would try to drain the command channel. It's yet unknown
|
// a loop that would try to drain the command channel. It's yet unknown
|
||||||
// if it's necessary/good practice to actively drain the waker queue.
|
// if it's necessary/good practice to actively drain the waker queue.
|
||||||
WAKER_TOKEN => 'waker: loop {
|
loop {
|
||||||
// take guard with every iteration so no new interest can be added
|
// take guard with every iteration so no new interest can be added
|
||||||
// until the current task is done.
|
// until the current task is done.
|
||||||
let mut guard = self.waker.guard();
|
let mut guard = self.waker.guard();
|
||||||
match guard.pop_front() {
|
match guard.pop_front() {
|
||||||
// worker notify it becomes available. we may want to recover
|
// worker notify it becomes available.
|
||||||
// from backpressure.
|
Some(WakerInterest::WorkerAvailable(idx)) => {
|
||||||
Some(WakerInterest::WorkerAvailable) => {
|
|
||||||
drop(guard);
|
drop(guard);
|
||||||
self.maybe_backpressure(&mut sockets, false);
|
|
||||||
|
self.avail.set_available(idx, true);
|
||||||
|
|
||||||
|
if !self.paused {
|
||||||
|
self.accept_all(sockets);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// a new worker thread is made and it's handle would be added to Accept
|
// a new worker thread is made and it's handle would be added to Accept
|
||||||
Some(WakerInterest::Worker(handle)) => {
|
Some(WakerInterest::Worker(handle)) => {
|
||||||
drop(guard);
|
drop(guard);
|
||||||
// maybe we want to recover from a backpressure.
|
|
||||||
self.maybe_backpressure(&mut sockets, false);
|
self.avail.set_available(handle.idx(), true);
|
||||||
self.handles.push(handle);
|
self.handles.push(handle);
|
||||||
|
|
||||||
|
if !self.paused {
|
||||||
|
self.accept_all(sockets);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// got timer interest and it's time to try register socket(s) again
|
// got timer interest and it's time to try register socket(s) again
|
||||||
Some(WakerInterest::Timer) => {
|
Some(WakerInterest::Timer) => {
|
||||||
drop(guard);
|
drop(guard);
|
||||||
self.process_timer(&mut sockets)
|
|
||||||
|
self.process_timer(sockets)
|
||||||
}
|
}
|
||||||
Some(WakerInterest::Pause) => {
|
Some(WakerInterest::Pause) => {
|
||||||
drop(guard);
|
drop(guard);
|
||||||
self.deregister_all(&mut sockets);
|
|
||||||
|
if !self.paused {
|
||||||
|
self.paused = true;
|
||||||
|
|
||||||
|
self.deregister_all(sockets);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some(WakerInterest::Resume) => {
|
Some(WakerInterest::Resume) => {
|
||||||
drop(guard);
|
drop(guard);
|
||||||
sockets.iter_mut().for_each(|(token, info)| {
|
|
||||||
self.register_logged(token, info);
|
if self.paused {
|
||||||
|
self.paused = false;
|
||||||
|
|
||||||
|
sockets.iter_mut().for_each(|info| {
|
||||||
|
self.register_logged(info);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.accept_all(sockets);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some(WakerInterest::Stop) => {
|
Some(WakerInterest::Stop) => {
|
||||||
return self.deregister_all(&mut sockets);
|
if !self.paused {
|
||||||
|
self.deregister_all(sockets);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
// waker queue is drained
|
// waker queue is drained
|
||||||
None => {
|
None => {
|
||||||
// Reset the WakerQueue before break so it does not grow infinitely
|
// Reset the WakerQueue before break so it does not grow infinitely
|
||||||
WakerQueue::reset(&mut guard);
|
WakerQueue::reset(&mut guard);
|
||||||
break 'waker;
|
|
||||||
}
|
return false;
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
let token = usize::from(token);
|
|
||||||
self.accept(&mut sockets, token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_timer(&self, sockets: &mut Slab<ServerSocketInfo>) {
|
fn process_timer(&self, sockets: &mut [ServerSocketInfo]) {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
sockets
|
sockets
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
// Only sockets that had an associated timeout were deregistered.
|
// Only sockets that had an associated timeout were deregistered.
|
||||||
.filter(|(_, info)| info.timeout.is_some())
|
.filter(|info| info.timeout.is_some())
|
||||||
.for_each(|(token, info)| {
|
.for_each(|info| {
|
||||||
let inst = info.timeout.take().unwrap();
|
let inst = info.timeout.take().unwrap();
|
||||||
|
|
||||||
if now < inst {
|
if now < inst {
|
||||||
info.timeout = Some(inst);
|
info.timeout = Some(inst);
|
||||||
} else if !self.backpressure {
|
} else if !self.paused {
|
||||||
self.register_logged(token, info);
|
self.register_logged(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop the timeout if server is in backpressure and socket timeout is expired.
|
// Drop the timeout if server is paused and socket timeout is expired.
|
||||||
// When server recovers from backpressure it will register all sockets without
|
// When server recovers from pause it will register all sockets without
|
||||||
// a timeout value so this socket register will be delayed till then.
|
// a timeout value so this socket register will be delayed till then.
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
fn register(&self, token: usize, info: &mut ServerSocketInfo) -> io::Result<()> {
|
fn register(&self, info: &mut ServerSocketInfo) -> io::Result<()> {
|
||||||
|
let token = MioToken(info.token);
|
||||||
self.poll
|
self.poll
|
||||||
.registry()
|
.registry()
|
||||||
.register(&mut info.lst, MioToken(token), Interest::READABLE)
|
.register(&mut info.lst, token, Interest::READABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn register(&self, token: usize, info: &mut ServerSocketInfo) -> io::Result<()> {
|
fn register(&self, info: &mut 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.
|
||||||
|
let token = MioToken(info.token);
|
||||||
self.poll
|
self.poll
|
||||||
.registry()
|
.registry()
|
||||||
.register(&mut info.lst, mio::Token(token), Interest::READABLE)
|
.register(&mut info.lst, token, Interest::READABLE)
|
||||||
.or_else(|_| {
|
.or_else(|_| {
|
||||||
self.poll.registry().reregister(
|
self.poll
|
||||||
&mut info.lst,
|
.registry()
|
||||||
mio::Token(token),
|
.reregister(&mut info.lst, token, Interest::READABLE)
|
||||||
Interest::READABLE,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_logged(&self, token: usize, info: &mut ServerSocketInfo) {
|
fn register_logged(&self, info: &mut ServerSocketInfo) {
|
||||||
match self.register(token, info) {
|
match self.register(info) {
|
||||||
Ok(_) => info!("Resume accepting connections on {}", info.addr),
|
Ok(_) => info!("Resume accepting connections on {}", info.lst.local_addr()),
|
||||||
Err(e) => error!("Can not register server socket {}", e),
|
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_logged(&self, info: &mut ServerSocketInfo) {
|
fn deregister_logged(&self, info: &mut ServerSocketInfo) {
|
||||||
match self.deregister(info) {
|
match self.poll.registry().deregister(&mut info.lst) {
|
||||||
Ok(_) => info!("Paused accepting connections on {}", info.addr),
|
Ok(_) => info!("Paused accepting connections on {}", info.lst.local_addr()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Can not deregister server socket {}", e)
|
error!("Can not deregister server socket {}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deregister_all(&self, sockets: &mut Slab<ServerSocketInfo>) {
|
fn deregister_all(&self, sockets: &mut [ServerSocketInfo]) {
|
||||||
sockets.iter_mut().for_each(|(_, info)| {
|
// This is a best effort implementation with following limitation:
|
||||||
self.deregister_logged(info);
|
//
|
||||||
});
|
// Every ServerSocketInfo with associate timeout will be skipped and it's timeout
|
||||||
}
|
// is removed in the process.
|
||||||
|
//
|
||||||
fn maybe_backpressure(&mut self, sockets: &mut Slab<ServerSocketInfo>, on: bool) {
|
// Therefore WakerInterest::Pause followed by WakerInterest::Resume in a very short
|
||||||
// Only operate when server is in a different backpressure than the given flag.
|
// gap (less than 500ms) would cause all timing out ServerSocketInfos be reregistered
|
||||||
if self.backpressure != on {
|
// before expected timing.
|
||||||
if on {
|
|
||||||
self.backpressure = true;
|
|
||||||
// TODO: figure out if timing out sockets can be safely de-registered twice.
|
|
||||||
self.deregister_all(sockets);
|
|
||||||
} else {
|
|
||||||
self.backpressure = false;
|
|
||||||
sockets
|
sockets
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
// Only operate on sockets without associated timeout.
|
// Take all timeout.
|
||||||
// Sockets with it will attempt to re-register when their timeout expires.
|
// This is to prevent Accept::process_timer method re-register a socket afterwards.
|
||||||
.filter(|(_, info)| info.timeout.is_none())
|
.map(|info| (info.timeout.take(), info))
|
||||||
.for_each(|(token, info)| self.register_logged(token, info));
|
// Socket info with a timeout is already deregistered so skip them.
|
||||||
}
|
.filter(|(timeout, _)| timeout.is_none())
|
||||||
}
|
.for_each(|(_, info)| self.deregister_logged(info));
|
||||||
}
|
|
||||||
|
|
||||||
fn accept_one(&mut self, sockets: &mut Slab<ServerSocketInfo>, mut conn: Conn) {
|
|
||||||
if self.backpressure {
|
|
||||||
// send_connection would remove fault worker from handles.
|
|
||||||
// worst case here is conn get dropped after all handles are gone.
|
|
||||||
while !self.handles.is_empty() {
|
|
||||||
match self.send_connection(sockets, conn) {
|
|
||||||
Ok(_) => return,
|
|
||||||
Err(c) => conn = c,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Do one round and try to send conn to all workers until it succeed.
|
|
||||||
// Start from self.next.
|
|
||||||
let mut idx = 0;
|
|
||||||
while idx < self.handles.len() {
|
|
||||||
idx += 1;
|
|
||||||
if self.handles[self.next].available() {
|
|
||||||
match self.send_connection(sockets, conn) {
|
|
||||||
Ok(_) => return,
|
|
||||||
Err(c) => conn = c,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.set_next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sending Conn failed due to either all workers are in error or not available.
|
|
||||||
// Enter backpressure state and try again.
|
|
||||||
self.maybe_backpressure(sockets, true);
|
|
||||||
self.accept_one(sockets, conn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set next worker handle that would accept work.
|
|
||||||
fn set_next(&mut self) {
|
|
||||||
self.next = (self.next + 1) % self.handles.len();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send connection to worker and handle error.
|
// Send connection to worker and handle error.
|
||||||
fn send_connection(
|
fn send_connection(&mut self, conn: Conn) -> Result<(), Conn> {
|
||||||
&mut self,
|
let next = self.next();
|
||||||
sockets: &mut Slab<ServerSocketInfo>,
|
match next.send(conn) {
|
||||||
conn: Conn,
|
|
||||||
) -> Result<(), Conn> {
|
|
||||||
match self.handles[self.next].send(conn) {
|
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
// Increment counter of WorkerHandle.
|
||||||
|
// Set worker to unavailable with it hit max (Return false).
|
||||||
|
if !next.inc_counter() {
|
||||||
|
let idx = next.idx();
|
||||||
|
self.avail.set_available(idx, false);
|
||||||
|
}
|
||||||
self.set_next();
|
self.set_next();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(conn) => {
|
Err(conn) => {
|
||||||
// worker lost contact and could be gone. a message is sent to
|
// Worker thread is error and could be gone.
|
||||||
// `ServerBuilder` future to notify it a new worker should be made.
|
// Remove worker handle and notify `ServerBuilder`.
|
||||||
// after that remove the fault worker and enter backpressure if necessary.
|
self.remove_next();
|
||||||
self.srv.worker_faulted(self.handles[self.next].idx);
|
|
||||||
self.handles.swap_remove(self.next);
|
|
||||||
if self.handles.is_empty() {
|
if self.handles.is_empty() {
|
||||||
error!("No workers");
|
error!("No workers");
|
||||||
self.maybe_backpressure(sockets, true);
|
|
||||||
// All workers are gone and Conn is nowhere to be sent.
|
// All workers are gone and Conn is nowhere to be sent.
|
||||||
// Treat this situation as Ok and drop Conn.
|
// Treat this situation as Ok and drop Conn.
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else if self.handles.len() <= self.next {
|
} else if self.handles.len() <= self.next {
|
||||||
self.next = 0;
|
self.next = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(conn)
|
Err(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accept(&mut self, sockets: &mut Slab<ServerSocketInfo>, token: usize) {
|
fn accept_one(&mut self, mut conn: Conn) {
|
||||||
loop {
|
loop {
|
||||||
let info = sockets
|
let next = self.next();
|
||||||
.get_mut(token)
|
let idx = next.idx();
|
||||||
.expect("ServerSocketInfo is removed from Slab");
|
|
||||||
|
if self.avail.get_available(idx) {
|
||||||
|
match self.send_connection(conn) {
|
||||||
|
Ok(_) => return,
|
||||||
|
Err(c) => conn = c,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.avail.set_available(idx, false);
|
||||||
|
self.set_next();
|
||||||
|
|
||||||
|
if !self.avail.available() {
|
||||||
|
while let Err(c) = self.send_connection(conn) {
|
||||||
|
conn = c;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accept(&mut self, sockets: &mut [ServerSocketInfo], token: usize) {
|
||||||
|
while self.avail.available() {
|
||||||
|
let info = &mut sockets[token];
|
||||||
|
|
||||||
match info.lst.accept() {
|
match info.lst.accept() {
|
||||||
Ok(io) => {
|
Ok(io) => {
|
||||||
let msg = Conn {
|
let conn = Conn { io, token };
|
||||||
io,
|
self.accept_one(conn);
|
||||||
token: info.token,
|
|
||||||
};
|
|
||||||
self.accept_one(sockets, msg);
|
|
||||||
}
|
}
|
||||||
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,
|
||||||
@@ -431,4 +494,99 @@ impl Accept {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn accept_all(&mut self, sockets: &mut [ServerSocketInfo]) {
|
||||||
|
sockets
|
||||||
|
.iter_mut()
|
||||||
|
.map(|info| info.token)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.for_each(|idx| self.accept(sockets, idx))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn next(&self) -> &WorkerHandleAccept {
|
||||||
|
&self.handles[self.next]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set next worker handle that would accept connection.
|
||||||
|
#[inline(always)]
|
||||||
|
fn set_next(&mut self) {
|
||||||
|
self.next = (self.next + 1) % self.handles.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove next worker handle that fail to accept connection.
|
||||||
|
fn remove_next(&mut self) {
|
||||||
|
let handle = self.handles.swap_remove(self.next);
|
||||||
|
let idx = handle.idx();
|
||||||
|
// A message is sent to `ServerBuilder` future to notify it a new worker
|
||||||
|
// should be made.
|
||||||
|
self.srv.worker_faulted(idx);
|
||||||
|
self.avail.set_available(idx, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::Availability;
|
||||||
|
|
||||||
|
fn single(aval: &mut Availability, idx: usize) {
|
||||||
|
aval.set_available(idx, true);
|
||||||
|
assert!(aval.available());
|
||||||
|
|
||||||
|
aval.set_available(idx, true);
|
||||||
|
|
||||||
|
aval.set_available(idx, false);
|
||||||
|
assert!(!aval.available());
|
||||||
|
|
||||||
|
aval.set_available(idx, false);
|
||||||
|
assert!(!aval.available());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn multi(aval: &mut Availability, mut idx: Vec<usize>) {
|
||||||
|
idx.iter().for_each(|idx| aval.set_available(*idx, true));
|
||||||
|
|
||||||
|
assert!(aval.available());
|
||||||
|
|
||||||
|
while let Some(idx) = idx.pop() {
|
||||||
|
assert!(aval.available());
|
||||||
|
aval.set_available(idx, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(!aval.available());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn availability() {
|
||||||
|
let mut aval = Availability::default();
|
||||||
|
|
||||||
|
single(&mut aval, 1);
|
||||||
|
single(&mut aval, 128);
|
||||||
|
single(&mut aval, 256);
|
||||||
|
single(&mut aval, 511);
|
||||||
|
|
||||||
|
let idx = (0..511).filter(|i| i % 3 == 0 && i % 5 == 0).collect();
|
||||||
|
|
||||||
|
multi(&mut aval, idx);
|
||||||
|
|
||||||
|
multi(&mut aval, (0..511).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn overflow() {
|
||||||
|
let mut aval = Availability::default();
|
||||||
|
single(&mut aval, 512);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pin_point() {
|
||||||
|
let mut aval = Availability::default();
|
||||||
|
|
||||||
|
aval.set_available(438, true);
|
||||||
|
|
||||||
|
aval.set_available(479, true);
|
||||||
|
|
||||||
|
assert_eq!(aval.0[3], 1 << (438 - 384) | 1 << (479 - 384));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,28 +8,29 @@ use std::{
|
|||||||
|
|
||||||
use actix_rt::{self as rt, net::TcpStream, time::sleep, System};
|
use actix_rt::{self as rt, net::TcpStream, time::sleep, System};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
|
use tokio::sync::{
|
||||||
use tokio::sync::oneshot;
|
mpsc::{unbounded_channel, UnboundedReceiver},
|
||||||
|
oneshot,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::accept::AcceptLoop;
|
use crate::accept::AcceptLoop;
|
||||||
use crate::config::{ConfiguredService, ServiceConfig};
|
use crate::join_all;
|
||||||
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::{MioListener, StdSocketAddr, StdTcpListener, ToSocketAddrs};
|
||||||
use crate::socket::{MioTcpListener, MioTcpSocket};
|
use crate::socket::{MioTcpListener, MioTcpSocket};
|
||||||
use crate::waker_queue::{WakerInterest, WakerQueue};
|
use crate::waker_queue::{WakerInterest, WakerQueue};
|
||||||
use crate::worker::{self, ServerWorker, ServerWorkerConfig, WorkerAvailability, WorkerHandle};
|
use crate::worker::{ServerWorker, ServerWorkerConfig, WorkerHandleAccept, WorkerHandleServer};
|
||||||
use crate::{join_all, Token};
|
|
||||||
|
|
||||||
/// Server builder
|
/// Server builder
|
||||||
pub struct ServerBuilder {
|
pub struct ServerBuilder {
|
||||||
threads: usize,
|
threads: usize,
|
||||||
token: Token,
|
token: usize,
|
||||||
backlog: u32,
|
backlog: u32,
|
||||||
handles: Vec<(usize, WorkerHandle)>,
|
handles: Vec<(usize, WorkerHandleServer)>,
|
||||||
services: Vec<Box<dyn InternalServiceFactory>>,
|
services: Vec<Box<dyn InternalServiceFactory>>,
|
||||||
sockets: Vec<(Token, String, MioListener)>,
|
sockets: Vec<(usize, String, MioListener)>,
|
||||||
accept: AcceptLoop,
|
accept: AcceptLoop,
|
||||||
exit: bool,
|
exit: bool,
|
||||||
no_signals: bool,
|
no_signals: bool,
|
||||||
@@ -53,7 +54,7 @@ impl ServerBuilder {
|
|||||||
|
|
||||||
ServerBuilder {
|
ServerBuilder {
|
||||||
threads: num_cpus::get(),
|
threads: num_cpus::get(),
|
||||||
token: Token::default(),
|
token: 0,
|
||||||
handles: Vec::new(),
|
handles: Vec::new(),
|
||||||
services: Vec::new(),
|
services: Vec::new(),
|
||||||
sockets: Vec::new(),
|
sockets: Vec::new(),
|
||||||
@@ -117,8 +118,8 @@ impl ServerBuilder {
|
|||||||
/// reached for each worker.
|
/// reached for each worker.
|
||||||
///
|
///
|
||||||
/// By default max connections is set to a 25k per worker.
|
/// By default max connections is set to a 25k per worker.
|
||||||
pub fn maxconn(self, num: usize) -> Self {
|
pub fn maxconn(mut self, num: usize) -> Self {
|
||||||
worker::max_concurrent_connections(num);
|
self.worker_config.max_concurrent_connections(num);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,32 +147,6 @@ impl ServerBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute external configuration as part of the server building process.
|
|
||||||
///
|
|
||||||
/// This function is useful for moving parts of configuration to a different module or
|
|
||||||
/// even library.
|
|
||||||
pub fn configure<F>(mut self, f: F) -> io::Result<ServerBuilder>
|
|
||||||
where
|
|
||||||
F: Fn(&mut ServiceConfig) -> io::Result<()>,
|
|
||||||
{
|
|
||||||
let mut cfg = ServiceConfig::new(self.threads, self.backlog);
|
|
||||||
|
|
||||||
f(&mut cfg)?;
|
|
||||||
|
|
||||||
if let Some(apply) = cfg.apply {
|
|
||||||
let mut srv = ConfiguredService::new(apply);
|
|
||||||
for (name, lst) in cfg.services {
|
|
||||||
let token = self.token.next();
|
|
||||||
srv.stream(token, name.clone(), lst.local_addr()?);
|
|
||||||
self.sockets.push((token, name, MioListener::Tcp(lst)));
|
|
||||||
}
|
|
||||||
self.services.push(Box::new(srv));
|
|
||||||
}
|
|
||||||
self.threads = cfg.threads;
|
|
||||||
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add new service to the server.
|
/// Add new service to the server.
|
||||||
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
|
||||||
@@ -181,7 +156,7 @@ impl ServerBuilder {
|
|||||||
let sockets = bind_addr(addr, self.backlog)?;
|
let sockets = bind_addr(addr, self.backlog)?;
|
||||||
|
|
||||||
for lst in sockets {
|
for lst in sockets {
|
||||||
let token = self.token.next();
|
let token = self.next_token();
|
||||||
self.services.push(StreamNewService::create(
|
self.services.push(StreamNewService::create(
|
||||||
name.as_ref().to_string(),
|
name.as_ref().to_string(),
|
||||||
token,
|
token,
|
||||||
@@ -230,7 +205,7 @@ impl ServerBuilder {
|
|||||||
{
|
{
|
||||||
use std::net::{IpAddr, Ipv4Addr};
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
lst.set_nonblocking(true)?;
|
lst.set_nonblocking(true)?;
|
||||||
let token = self.token.next();
|
let token = self.next_token();
|
||||||
let addr = StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
|
let addr = StdSocketAddr::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(),
|
||||||
@@ -256,7 +231,7 @@ impl ServerBuilder {
|
|||||||
lst.set_nonblocking(true)?;
|
lst.set_nonblocking(true)?;
|
||||||
let addr = lst.local_addr()?;
|
let addr = lst.local_addr()?;
|
||||||
|
|
||||||
let token = self.token.next();
|
let token = self.next_token();
|
||||||
self.services.push(StreamNewService::create(
|
self.services.push(StreamNewService::create(
|
||||||
name.as_ref().to_string(),
|
name.as_ref().to_string(),
|
||||||
token,
|
token,
|
||||||
@@ -280,10 +255,11 @@ impl ServerBuilder {
|
|||||||
// start workers
|
// start workers
|
||||||
let handles = (0..self.threads)
|
let handles = (0..self.threads)
|
||||||
.map(|idx| {
|
.map(|idx| {
|
||||||
let handle = self.start_worker(idx, self.accept.waker_owned());
|
let (handle_accept, handle_server) =
|
||||||
self.handles.push((idx, handle.clone()));
|
self.start_worker(idx, self.accept.waker_owned());
|
||||||
|
self.handles.push((idx, handle_server));
|
||||||
|
|
||||||
handle
|
handle_accept
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -311,11 +287,14 @@ impl ServerBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_worker(&self, idx: usize, waker: WakerQueue) -> WorkerHandle {
|
fn start_worker(
|
||||||
let avail = WorkerAvailability::new(waker);
|
&self,
|
||||||
|
idx: usize,
|
||||||
|
waker_queue: WakerQueue,
|
||||||
|
) -> (WorkerHandleAccept, WorkerHandleServer) {
|
||||||
let services = self.services.iter().map(|v| v.clone_factory()).collect();
|
let services = self.services.iter().map(|v| v.clone_factory()).collect();
|
||||||
|
|
||||||
ServerWorker::start(idx, services, avail, self.worker_config)
|
ServerWorker::start(idx, services, waker_queue, self.worker_config)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_cmd(&mut self, item: ServerCommand) {
|
fn handle_cmd(&mut self, item: ServerCommand) {
|
||||||
@@ -373,45 +352,29 @@ impl ServerBuilder {
|
|||||||
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 {
|
let stop = self
|
||||||
let iter = self
|
|
||||||
.handles
|
.handles
|
||||||
.iter()
|
.iter()
|
||||||
.map(move |worker| worker.1.stop(graceful))
|
.map(move |worker| worker.1.stop(graceful))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let fut = join_all(iter);
|
|
||||||
|
|
||||||
rt::spawn(async move {
|
rt::spawn(async move {
|
||||||
let _ = fut.await;
|
if graceful {
|
||||||
|
let _ = join_all(stop).await;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(tx) = completion {
|
if let Some(tx) = completion {
|
||||||
let _ = tx.send(());
|
let _ = tx.send(());
|
||||||
}
|
}
|
||||||
for tx in notify {
|
for tx in notify {
|
||||||
let _ = tx.send(());
|
let _ = tx.send(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if exit {
|
if exit {
|
||||||
rt::spawn(async {
|
|
||||||
sleep(Duration::from_millis(300)).await;
|
sleep(Duration::from_millis(300)).await;
|
||||||
System::current().stop();
|
System::current().stop();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
// we need to stop system if server was spawned
|
|
||||||
if self.exit {
|
|
||||||
rt::spawn(async {
|
|
||||||
sleep(Duration::from_millis(300)).await;
|
|
||||||
System::current().stop();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if let Some(tx) = completion {
|
|
||||||
let _ = tx.send(());
|
|
||||||
}
|
|
||||||
for tx in notify {
|
|
||||||
let _ = tx.send(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ServerCommand::WorkerFaulted(idx) => {
|
ServerCommand::WorkerFaulted(idx) => {
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
@@ -437,13 +400,20 @@ impl ServerBuilder {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let handle = self.start_worker(new_idx, self.accept.waker_owned());
|
let (handle_accept, handle_server) =
|
||||||
self.handles.push((new_idx, handle.clone()));
|
self.start_worker(new_idx, self.accept.waker_owned());
|
||||||
self.accept.wake(WakerInterest::Worker(handle));
|
self.handles.push((new_idx, handle_server));
|
||||||
|
self.accept.wake(WakerInterest::Worker(handle_accept));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_token(&mut self) -> usize {
|
||||||
|
let token = self.token;
|
||||||
|
self.token += 1;
|
||||||
|
token
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Future for ServerBuilder {
|
impl Future for ServerBuilder {
|
||||||
|
@@ -1,287 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::future::Future;
|
|
||||||
use std::{fmt, io};
|
|
||||||
|
|
||||||
use actix_rt::net::TcpStream;
|
|
||||||
use actix_service::{
|
|
||||||
fn_service, IntoServiceFactory as IntoBaseServiceFactory,
|
|
||||||
ServiceFactory as BaseServiceFactory,
|
|
||||||
};
|
|
||||||
use actix_utils::counter::CounterGuard;
|
|
||||||
use futures_core::future::LocalBoxFuture;
|
|
||||||
use log::error;
|
|
||||||
|
|
||||||
use crate::builder::bind_addr;
|
|
||||||
use crate::service::{BoxedServerService, InternalServiceFactory, StreamService};
|
|
||||||
use crate::socket::{MioStream, MioTcpListener, StdSocketAddr, StdTcpListener, ToSocketAddrs};
|
|
||||||
use crate::{ready, Token};
|
|
||||||
|
|
||||||
pub struct ServiceConfig {
|
|
||||||
pub(crate) services: Vec<(String, MioTcpListener)>,
|
|
||||||
pub(crate) apply: Option<Box<dyn ServiceRuntimeConfiguration>>,
|
|
||||||
pub(crate) threads: usize,
|
|
||||||
pub(crate) backlog: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ServiceConfig {
|
|
||||||
pub(super) fn new(threads: usize, backlog: u32) -> ServiceConfig {
|
|
||||||
ServiceConfig {
|
|
||||||
threads,
|
|
||||||
backlog,
|
|
||||||
services: Vec::new(),
|
|
||||||
apply: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set number of workers to start.
|
|
||||||
///
|
|
||||||
/// By default server uses number of available logical cpu as workers
|
|
||||||
/// count.
|
|
||||||
pub fn workers(&mut self, num: usize) {
|
|
||||||
self.threads = num;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add new service to server
|
|
||||||
pub fn bind<U, N: AsRef<str>>(&mut self, name: N, addr: U) -> io::Result<&mut Self>
|
|
||||||
where
|
|
||||||
U: ToSocketAddrs,
|
|
||||||
{
|
|
||||||
let sockets = bind_addr(addr, self.backlog)?;
|
|
||||||
|
|
||||||
for lst in sockets {
|
|
||||||
self._listen(name.as_ref(), lst);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add new service to server
|
|
||||||
pub fn listen<N: AsRef<str>>(&mut self, name: N, lst: StdTcpListener) -> &mut Self {
|
|
||||||
self._listen(name, MioTcpListener::from_std(lst))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register service configuration function. This function get called
|
|
||||||
/// during worker runtime configuration. It get executed in worker thread.
|
|
||||||
pub fn apply<F>(&mut self, f: F) -> io::Result<()>
|
|
||||||
where
|
|
||||||
F: Fn(&mut ServiceRuntime) + Send + Clone + 'static,
|
|
||||||
{
|
|
||||||
self.apply = Some(Box::new(f));
|
|
||||||
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 {
|
|
||||||
rt: Box<dyn ServiceRuntimeConfiguration>,
|
|
||||||
names: HashMap<Token, (String, StdSocketAddr)>,
|
|
||||||
topics: HashMap<String, Token>,
|
|
||||||
services: Vec<Token>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfiguredService {
|
|
||||||
pub(super) fn new(rt: Box<dyn ServiceRuntimeConfiguration>) -> Self {
|
|
||||||
ConfiguredService {
|
|
||||||
rt,
|
|
||||||
names: HashMap::new(),
|
|
||||||
topics: HashMap::new(),
|
|
||||||
services: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn stream(&mut self, token: Token, name: String, addr: StdSocketAddr) {
|
|
||||||
self.names.insert(token, (name.clone(), addr));
|
|
||||||
self.topics.insert(name, token);
|
|
||||||
self.services.push(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InternalServiceFactory for ConfiguredService {
|
|
||||||
fn name(&self, token: Token) -> &str {
|
|
||||||
&self.names[&token].0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone_factory(&self) -> Box<dyn InternalServiceFactory> {
|
|
||||||
Box::new(Self {
|
|
||||||
rt: self.rt.clone(),
|
|
||||||
names: self.names.clone(),
|
|
||||||
topics: self.topics.clone(),
|
|
||||||
services: self.services.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> {
|
|
||||||
// configure services
|
|
||||||
let mut rt = ServiceRuntime::new(self.topics.clone());
|
|
||||||
self.rt.configure(&mut rt);
|
|
||||||
rt.validate();
|
|
||||||
let mut names = self.names.clone();
|
|
||||||
let tokens = self.services.clone();
|
|
||||||
|
|
||||||
// construct services
|
|
||||||
Box::pin(async move {
|
|
||||||
let mut services = rt.services;
|
|
||||||
// TODO: Proper error handling here
|
|
||||||
for f in rt.onstart.into_iter() {
|
|
||||||
f.await;
|
|
||||||
}
|
|
||||||
let mut res = vec![];
|
|
||||||
for token in tokens {
|
|
||||||
if let Some(srv) = services.remove(&token) {
|
|
||||||
let newserv = srv.new_service(());
|
|
||||||
match newserv.await {
|
|
||||||
Ok(serv) => {
|
|
||||||
res.push((token, serv));
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
error!("Can not construct service");
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let name = names.remove(&token).unwrap().0;
|
|
||||||
res.push((
|
|
||||||
token,
|
|
||||||
Box::new(StreamService::new(fn_service(move |_: TcpStream| {
|
|
||||||
error!("Service {:?} is not configured", name);
|
|
||||||
ready::<Result<_, ()>>(Ok(()))
|
|
||||||
}))),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) trait ServiceRuntimeConfiguration: Send {
|
|
||||||
fn clone(&self) -> Box<dyn ServiceRuntimeConfiguration>;
|
|
||||||
|
|
||||||
fn configure(&self, rt: &mut ServiceRuntime);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F> ServiceRuntimeConfiguration for F
|
|
||||||
where
|
|
||||||
F: Fn(&mut ServiceRuntime) + Send + Clone + 'static,
|
|
||||||
{
|
|
||||||
fn clone(&self) -> Box<dyn ServiceRuntimeConfiguration> {
|
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure(&self, rt: &mut ServiceRuntime) {
|
|
||||||
(self)(rt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn not_configured(_: &mut ServiceRuntime) {
|
|
||||||
error!("Service is not configured");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ServiceRuntime {
|
|
||||||
names: HashMap<String, Token>,
|
|
||||||
services: HashMap<Token, BoxedNewService>,
|
|
||||||
onstart: Vec<LocalBoxFuture<'static, ()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ServiceRuntime {
|
|
||||||
fn new(names: HashMap<String, Token>) -> Self {
|
|
||||||
ServiceRuntime {
|
|
||||||
names,
|
|
||||||
services: HashMap::new(),
|
|
||||||
onstart: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate(&self) {
|
|
||||||
for (name, token) in &self.names {
|
|
||||||
if !self.services.contains_key(&token) {
|
|
||||||
error!("Service {:?} is not configured", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register service.
|
|
||||||
///
|
|
||||||
/// Name of the service must be registered during configuration stage with
|
|
||||||
/// *ServiceConfig::bind()* or *ServiceConfig::listen()* methods.
|
|
||||||
pub fn service<T, F>(&mut self, name: &str, service: F)
|
|
||||||
where
|
|
||||||
F: IntoBaseServiceFactory<T, TcpStream>,
|
|
||||||
T: BaseServiceFactory<TcpStream, Config = ()> + 'static,
|
|
||||||
T::Future: 'static,
|
|
||||||
T::Service: 'static,
|
|
||||||
T::InitError: fmt::Debug,
|
|
||||||
{
|
|
||||||
// let name = name.to_owned();
|
|
||||||
if let Some(token) = self.names.get(name) {
|
|
||||||
self.services.insert(
|
|
||||||
*token,
|
|
||||||
Box::new(ServiceFactory {
|
|
||||||
inner: service.into_factory(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
panic!("Unknown service: {:?}", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute future before services initialization.
|
|
||||||
pub fn on_start<F>(&mut self, fut: F)
|
|
||||||
where
|
|
||||||
F: Future<Output = ()> + 'static,
|
|
||||||
{
|
|
||||||
self.onstart.push(Box::pin(fut))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type BoxedNewService = Box<
|
|
||||||
dyn BaseServiceFactory<
|
|
||||||
(Option<CounterGuard>, MioStream),
|
|
||||||
Response = (),
|
|
||||||
Error = (),
|
|
||||||
InitError = (),
|
|
||||||
Config = (),
|
|
||||||
Service = BoxedServerService,
|
|
||||||
Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>,
|
|
||||||
>,
|
|
||||||
>;
|
|
||||||
|
|
||||||
struct ServiceFactory<T> {
|
|
||||||
inner: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> BaseServiceFactory<(Option<CounterGuard>, MioStream)> for ServiceFactory<T>
|
|
||||||
where
|
|
||||||
T: BaseServiceFactory<TcpStream, Config = ()>,
|
|
||||||
T::Future: 'static,
|
|
||||||
T::Service: 'static,
|
|
||||||
T::Error: 'static,
|
|
||||||
T::InitError: fmt::Debug + 'static,
|
|
||||||
{
|
|
||||||
type Response = ();
|
|
||||||
type Error = ();
|
|
||||||
type Config = ();
|
|
||||||
type Service = BoxedServerService;
|
|
||||||
type InitError = ();
|
|
||||||
type Future = LocalBoxFuture<'static, Result<BoxedServerService, ()>>;
|
|
||||||
|
|
||||||
fn new_service(&self, _: ()) -> Self::Future {
|
|
||||||
let fut = self.inner.new_service(());
|
|
||||||
Box::pin(async move {
|
|
||||||
match fut.await {
|
|
||||||
Ok(s) => Ok(Box::new(StreamService::new(s)) as BoxedServerService),
|
|
||||||
Err(e) => {
|
|
||||||
error!("Can not construct service: {:?}", e);
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
mod accept;
|
mod accept;
|
||||||
mod builder;
|
mod builder;
|
||||||
mod config;
|
|
||||||
mod server;
|
mod server;
|
||||||
mod service;
|
mod service;
|
||||||
mod signals;
|
mod signals;
|
||||||
@@ -16,7 +15,6 @@ 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::server::Server;
|
pub use self::server::Server;
|
||||||
pub use self::service::ServiceFactory;
|
pub use self::service::ServiceFactory;
|
||||||
pub use self::test_server::TestServer;
|
pub use self::test_server::TestServer;
|
||||||
@@ -28,51 +26,11 @@ use std::future::Future;
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
/// Socket ID token
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct Token(usize);
|
|
||||||
|
|
||||||
impl Default for Token {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Token {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn next(&mut self) -> Token {
|
|
||||||
let token = Token(self.0);
|
|
||||||
self.0 += 1;
|
|
||||||
token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start server building process
|
/// Start server building process
|
||||||
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.
|
// 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.
|
// pin_project and pinned futures are overkill for this task.
|
||||||
pub(crate) struct JoinAll<T> {
|
pub(crate) struct JoinAll<T> {
|
||||||
@@ -132,6 +90,8 @@ impl<T> Future for JoinAll<T> {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use actix_utils::future::ready;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_join_all() {
|
async fn test_join_all() {
|
||||||
let futs = vec![ready(Ok(1)), ready(Err(3)), ready(Ok(9))];
|
let futs = vec![ready(Ok(1)), ready(Err(3)), ready(Ok(9))];
|
||||||
|
@@ -3,12 +3,12 @@ use std::net::SocketAddr;
|
|||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use actix_service::{Service, ServiceFactory as BaseServiceFactory};
|
use actix_service::{Service, ServiceFactory as BaseServiceFactory};
|
||||||
use actix_utils::counter::CounterGuard;
|
use actix_utils::future::{ready, Ready};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
use log::error;
|
use log::error;
|
||||||
|
|
||||||
use crate::socket::{FromStream, MioStream};
|
use crate::socket::{FromStream, MioStream};
|
||||||
use crate::{ready, Ready, Token};
|
use crate::worker::WorkerCounterGuard;
|
||||||
|
|
||||||
pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static {
|
pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static {
|
||||||
type Factory: BaseServiceFactory<Stream, Config = ()>;
|
type Factory: BaseServiceFactory<Stream, Config = ()>;
|
||||||
@@ -17,16 +17,16 @@ pub trait ServiceFactory<Stream: FromStream>: Send + Clone + 'static {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait InternalServiceFactory: Send {
|
pub(crate) trait InternalServiceFactory: Send {
|
||||||
fn name(&self, token: Token) -> &str;
|
fn name(&self, token: usize) -> &str;
|
||||||
|
|
||||||
fn clone_factory(&self) -> Box<dyn InternalServiceFactory>;
|
fn clone_factory(&self) -> Box<dyn InternalServiceFactory>;
|
||||||
|
|
||||||
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>>;
|
fn create(&self) -> LocalBoxFuture<'static, Result<(usize, BoxedServerService), ()>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type BoxedServerService = Box<
|
pub(crate) type BoxedServerService = Box<
|
||||||
dyn Service<
|
dyn Service<
|
||||||
(Option<CounterGuard>, MioStream),
|
(WorkerCounterGuard, MioStream),
|
||||||
Response = (),
|
Response = (),
|
||||||
Error = (),
|
Error = (),
|
||||||
Future = Ready<Result<(), ()>>,
|
Future = Ready<Result<(), ()>>,
|
||||||
@@ -47,7 +47,7 @@ impl<S, I> StreamService<S, I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, I> Service<(Option<CounterGuard>, MioStream)> for StreamService<S, I>
|
impl<S, I> Service<(WorkerCounterGuard, MioStream)> for StreamService<S, I>
|
||||||
where
|
where
|
||||||
S: Service<I>,
|
S: Service<I>,
|
||||||
S::Future: 'static,
|
S::Future: 'static,
|
||||||
@@ -62,7 +62,7 @@ where
|
|||||||
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(&self, (guard, req): (WorkerCounterGuard, MioStream)) -> Self::Future {
|
||||||
ready(match FromStream::from_mio(req) {
|
ready(match FromStream::from_mio(req) {
|
||||||
Ok(stream) => {
|
Ok(stream) => {
|
||||||
let f = self.service.call(stream);
|
let f = self.service.call(stream);
|
||||||
@@ -83,7 +83,7 @@ where
|
|||||||
pub(crate) struct StreamNewService<F: ServiceFactory<Io>, Io: FromStream> {
|
pub(crate) struct StreamNewService<F: ServiceFactory<Io>, Io: FromStream> {
|
||||||
name: String,
|
name: String,
|
||||||
inner: F,
|
inner: F,
|
||||||
token: Token,
|
token: usize,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
_t: PhantomData<Io>,
|
_t: PhantomData<Io>,
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ where
|
|||||||
{
|
{
|
||||||
pub(crate) fn create(
|
pub(crate) fn create(
|
||||||
name: String,
|
name: String,
|
||||||
token: Token,
|
token: usize,
|
||||||
inner: F,
|
inner: F,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
) -> Box<dyn InternalServiceFactory> {
|
) -> Box<dyn InternalServiceFactory> {
|
||||||
@@ -114,7 +114,7 @@ where
|
|||||||
F: ServiceFactory<Io>,
|
F: ServiceFactory<Io>,
|
||||||
Io: FromStream + Send + 'static,
|
Io: FromStream + Send + 'static,
|
||||||
{
|
{
|
||||||
fn name(&self, _: Token) -> &str {
|
fn name(&self, _: usize) -> &str {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,14 +128,14 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(&self) -> LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>> {
|
fn create(&self) -> LocalBoxFuture<'static, Result<(usize, BoxedServerService), ()>> {
|
||||||
let token = self.token;
|
let token = self.token;
|
||||||
let fut = self.inner.create().new_service(());
|
let fut = self.inner.create().new_service(());
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
match fut.await {
|
match fut.await {
|
||||||
Ok(inner) => {
|
Ok(inner) => {
|
||||||
let service = Box::new(StreamService::new(inner)) as _;
|
let service = Box::new(StreamService::new(inner)) as _;
|
||||||
Ok(vec![(token, service)])
|
Ok((token, service))
|
||||||
}
|
}
|
||||||
Err(_) => Err(()),
|
Err(_) => Err(()),
|
||||||
}
|
}
|
||||||
|
@@ -12,18 +12,7 @@ pub(crate) use {
|
|||||||
use std::{fmt, io};
|
use std::{fmt, io};
|
||||||
|
|
||||||
use actix_rt::net::TcpStream;
|
use actix_rt::net::TcpStream;
|
||||||
use mio::event::Source;
|
use mio::{event::Source, Interest, Registry, Token};
|
||||||
use mio::net::TcpStream as MioTcpStream;
|
|
||||||
use mio::{Interest, Registry, Token};
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
|
||||||
#[cfg(unix)]
|
|
||||||
use {
|
|
||||||
actix_rt::net::UnixStream,
|
|
||||||
mio::net::{SocketAddr as MioSocketAddr, UnixStream as MioUnixStream},
|
|
||||||
std::os::unix::io::{FromRawFd, IntoRawFd},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) enum MioListener {
|
pub(crate) enum MioListener {
|
||||||
Tcp(MioTcpListener),
|
Tcp(MioTcpListener),
|
||||||
@@ -34,9 +23,15 @@ pub(crate) enum MioListener {
|
|||||||
impl MioListener {
|
impl MioListener {
|
||||||
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()),
|
MioListener::Tcp(ref lst) => lst
|
||||||
|
.local_addr()
|
||||||
|
.map(SocketAddr::Tcp)
|
||||||
|
.unwrap_or(SocketAddr::Unknown),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
MioListener::Uds(ref lst) => SocketAddr::Uds(lst.local_addr().unwrap()),
|
MioListener::Uds(ref lst) => lst
|
||||||
|
.local_addr()
|
||||||
|
.map(SocketAddr::Uds)
|
||||||
|
.unwrap_or(SocketAddr::Unknown),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,25 +116,27 @@ impl fmt::Debug for MioListener {
|
|||||||
impl fmt::Display for MioListener {
|
impl fmt::Display for MioListener {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
MioListener::Tcp(ref lst) => write!(f, "{}", lst.local_addr().ok().unwrap()),
|
MioListener::Tcp(ref lst) => write!(f, "{:?}", lst),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
MioListener::Uds(ref lst) => write!(f, "{:?}", lst.local_addr().ok().unwrap()),
|
MioListener::Uds(ref lst) => write!(f, "{:?}", lst),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum SocketAddr {
|
pub(crate) enum SocketAddr {
|
||||||
|
Unknown,
|
||||||
Tcp(StdSocketAddr),
|
Tcp(StdSocketAddr),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
Uds(MioSocketAddr),
|
Uds(mio::net::SocketAddr),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SocketAddr {
|
impl fmt::Display for SocketAddr {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
SocketAddr::Tcp(ref addr) => write!(f, "{}", addr),
|
Self::Unknown => write!(f, "Unknown SocketAddr"),
|
||||||
|
Self::Tcp(ref addr) => write!(f, "{}", addr),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
|
Self::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,18 +144,19 @@ impl fmt::Display for SocketAddr {
|
|||||||
impl fmt::Debug for SocketAddr {
|
impl fmt::Debug for SocketAddr {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
SocketAddr::Tcp(ref addr) => write!(f, "{:?}", addr),
|
Self::Unknown => write!(f, "Unknown SocketAddr"),
|
||||||
|
Self::Tcp(ref addr) => write!(f, "{:?}", addr),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
SocketAddr::Uds(ref addr) => write!(f, "{:?}", addr),
|
Self::Uds(ref addr) => write!(f, "{:?}", addr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum MioStream {
|
pub enum MioStream {
|
||||||
Tcp(MioTcpStream),
|
Tcp(mio::net::TcpStream),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
Uds(MioUnixStream),
|
Uds(mio::net::UnixStream),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// helper trait for converting mio stream to tokio stream.
|
/// helper trait for converting mio stream to tokio stream.
|
||||||
@@ -166,8 +164,35 @@ pub trait FromStream: Sized {
|
|||||||
fn from_mio(sock: MioStream) -> io::Result<Self>;
|
fn from_mio(sock: MioStream) -> io::Result<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod win_impl {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
||||||
|
|
||||||
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
||||||
|
impl FromStream for TcpStream {
|
||||||
|
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
||||||
|
match sock {
|
||||||
|
MioStream::Tcp(mio) => {
|
||||||
|
let raw = IntoRawSocket::into_raw_socket(mio);
|
||||||
|
// SAFETY: This is a in place conversion from mio stream to tokio stream.
|
||||||
|
TcpStream::from_std(unsafe { FromRawSocket::from_raw_socket(raw) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
mod unix_impl {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||||
|
|
||||||
|
use actix_rt::net::UnixStream;
|
||||||
|
|
||||||
|
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
||||||
impl FromStream for TcpStream {
|
impl FromStream for TcpStream {
|
||||||
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
||||||
match sock {
|
match sock {
|
||||||
@@ -184,21 +209,6 @@ impl FromStream for TcpStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
// FIXME: This is a workaround and we need an efficient way to convert between mio and tokio stream
|
||||||
#[cfg(windows)]
|
|
||||||
impl FromStream for TcpStream {
|
|
||||||
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
|
||||||
match sock {
|
|
||||||
MioStream::Tcp(mio) => {
|
|
||||||
let raw = IntoRawSocket::into_raw_socket(mio);
|
|
||||||
// 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 {
|
impl FromStream for UnixStream {
|
||||||
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
fn from_mio(sock: MioStream) -> io::Result<Self> {
|
||||||
match sock {
|
match sock {
|
||||||
@@ -211,6 +221,7 @@ impl FromStream for UnixStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@@ -6,7 +6,7 @@ use std::{
|
|||||||
|
|
||||||
use mio::{Registry, Token as MioToken, Waker};
|
use mio::{Registry, Token as MioToken, Waker};
|
||||||
|
|
||||||
use crate::worker::WorkerHandle;
|
use crate::worker::WorkerHandleAccept;
|
||||||
|
|
||||||
/// Waker token for `mio::Poll` instance.
|
/// Waker token for `mio::Poll` instance.
|
||||||
pub(crate) const WAKER_TOKEN: MioToken = MioToken(usize::MAX);
|
pub(crate) const WAKER_TOKEN: MioToken = MioToken(usize::MAX);
|
||||||
@@ -72,7 +72,7 @@ impl WakerQueue {
|
|||||||
pub(crate) enum WakerInterest {
|
pub(crate) enum WakerInterest {
|
||||||
/// `WorkerAvailable` is an interest from `Worker` notifying `Accept` there is a worker
|
/// `WorkerAvailable` is an interest from `Worker` notifying `Accept` there is a worker
|
||||||
/// available and can accept new tasks.
|
/// available and can accept new tasks.
|
||||||
WorkerAvailable,
|
WorkerAvailable(usize),
|
||||||
/// `Pause`, `Resume`, `Stop` Interest are from `ServerBuilder` future. It listens to
|
/// `Pause`, `Resume`, `Stop` Interest are from `ServerBuilder` future. It listens to
|
||||||
/// `ServerCommand` and notify `Accept` to do exactly these tasks.
|
/// `ServerCommand` and notify `Accept` to do exactly these tasks.
|
||||||
Pause,
|
Pause,
|
||||||
@@ -84,6 +84,6 @@ pub(crate) enum WakerInterest {
|
|||||||
Timer,
|
Timer,
|
||||||
/// `Worker` is an interest happen after a worker runs into faulted state(This is determined
|
/// `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
|
/// by if work can be sent to it successfully).`Accept` would be waked up and add the new
|
||||||
/// `WorkerHandle`.
|
/// `WorkerHandleAccept`.
|
||||||
Worker(WorkerHandle),
|
Worker(WorkerHandleAccept),
|
||||||
}
|
}
|
||||||
|
@@ -1,123 +1,195 @@
|
|||||||
use std::future::Future;
|
use std::{
|
||||||
use std::pin::Pin;
|
future::Future,
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
mem,
|
||||||
use std::sync::Arc;
|
pin::Pin,
|
||||||
use std::task::{Context, Poll};
|
rc::Rc,
|
||||||
use std::time::Duration;
|
sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
task::{Context, Poll},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use actix_rt::time::{sleep, Sleep};
|
use actix_rt::{
|
||||||
use actix_rt::{spawn, Arbiter};
|
spawn,
|
||||||
use actix_utils::counter::Counter;
|
time::{sleep, Instant, Sleep},
|
||||||
use futures_core::future::LocalBoxFuture;
|
Arbiter,
|
||||||
|
};
|
||||||
|
use futures_core::{future::LocalBoxFuture, ready};
|
||||||
use log::{error, info, trace};
|
use log::{error, info, trace};
|
||||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
use tokio::sync::{
|
||||||
use tokio::sync::oneshot;
|
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||||
|
oneshot,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::join_all;
|
||||||
use crate::service::{BoxedServerService, InternalServiceFactory};
|
use crate::service::{BoxedServerService, InternalServiceFactory};
|
||||||
use crate::socket::MioStream;
|
use crate::socket::MioStream;
|
||||||
use crate::waker_queue::{WakerInterest, WakerQueue};
|
use crate::waker_queue::{WakerInterest, WakerQueue};
|
||||||
use crate::{join_all, Token};
|
|
||||||
|
|
||||||
pub(crate) struct WorkerCommand(Conn);
|
/// Stop worker message. Returns `true` on successful graceful shutdown.
|
||||||
|
/// and `false` if some connections still alive when shutdown execute.
|
||||||
/// Stop worker message. Returns `true` on successful shutdown
|
pub(crate) struct Stop {
|
||||||
/// and `false` if some connections still alive.
|
|
||||||
pub(crate) struct StopCommand {
|
|
||||||
graceful: bool,
|
graceful: bool,
|
||||||
result: oneshot::Sender<bool>,
|
tx: oneshot::Sender<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Conn {
|
pub(crate) struct Conn {
|
||||||
pub io: MioStream,
|
pub io: MioStream,
|
||||||
pub token: Token,
|
pub token: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
static MAX_CONNS: AtomicUsize = AtomicUsize::new(25600);
|
fn handle_pair(
|
||||||
|
|
||||||
/// Sets the maximum per-worker number of concurrent connections.
|
|
||||||
///
|
|
||||||
/// All socket listeners will stop accepting connections when this limit is
|
|
||||||
/// reached for each worker.
|
|
||||||
///
|
|
||||||
/// By default max connections is set to a 25k per worker.
|
|
||||||
pub fn max_concurrent_connections(num: usize) {
|
|
||||||
MAX_CONNS.store(num, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
static MAX_CONNS_COUNTER: Counter =
|
|
||||||
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)]
|
|
||||||
pub(crate) struct WorkerHandle {
|
|
||||||
pub idx: usize,
|
|
||||||
tx1: UnboundedSender<WorkerCommand>,
|
|
||||||
tx2: UnboundedSender<StopCommand>,
|
|
||||||
avail: WorkerAvailability,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WorkerHandle {
|
|
||||||
pub fn new(
|
|
||||||
idx: usize,
|
idx: usize,
|
||||||
tx1: UnboundedSender<WorkerCommand>,
|
tx1: UnboundedSender<Conn>,
|
||||||
tx2: UnboundedSender<StopCommand>,
|
tx2: UnboundedSender<Stop>,
|
||||||
avail: WorkerAvailability,
|
counter: Counter,
|
||||||
) -> Self {
|
) -> (WorkerHandleAccept, WorkerHandleServer) {
|
||||||
WorkerHandle {
|
let accept = WorkerHandleAccept {
|
||||||
idx,
|
idx,
|
||||||
tx1,
|
tx: tx1,
|
||||||
tx2,
|
counter,
|
||||||
avail,
|
};
|
||||||
}
|
|
||||||
}
|
let server = WorkerHandleServer { idx, tx: tx2 };
|
||||||
|
|
||||||
pub fn send(&self, msg: Conn) -> Result<(), Conn> {
|
(accept, server)
|
||||||
self.tx1.send(WorkerCommand(msg)).map_err(|msg| msg.0 .0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn available(&self) -> bool {
|
|
||||||
self.avail.available()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(&self, graceful: bool) -> oneshot::Receiver<bool> {
|
|
||||||
let (result, rx) = oneshot::channel();
|
|
||||||
let _ = self.tx2.send(StopCommand { graceful, result });
|
|
||||||
rx
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// counter: Arc<AtomicUsize> field is owned by `Accept` thread and `ServerWorker` thread.
|
||||||
|
///
|
||||||
|
/// `Accept` would increment the counter and `ServerWorker` would decrement it.
|
||||||
|
///
|
||||||
|
/// # Atomic Ordering:
|
||||||
|
///
|
||||||
|
/// `Accept` always look into it's cached `Availability` field for `ServerWorker` state.
|
||||||
|
/// It lazily increment counter after successful dispatching new work to `ServerWorker`.
|
||||||
|
/// On reaching counter limit `Accept` update it's cached `Availability` and mark worker as
|
||||||
|
/// unable to accept any work.
|
||||||
|
///
|
||||||
|
/// `ServerWorker` always decrement the counter when every work received from `Accept` is done.
|
||||||
|
/// On reaching counter limit worker would use `mio::Waker` and `WakerQueue` to wake up `Accept`
|
||||||
|
/// and notify it to update cached `Availability` again to mark worker as able to accept work again.
|
||||||
|
///
|
||||||
|
/// Hence, a wake up would only happen after `Accept` increment it to limit.
|
||||||
|
/// And a decrement to limit always wake up `Accept`.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct WorkerAvailability {
|
pub(crate) struct Counter {
|
||||||
waker: WakerQueue,
|
counter: Arc<AtomicUsize>,
|
||||||
available: Arc<AtomicBool>,
|
limit: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkerAvailability {
|
impl Counter {
|
||||||
pub fn new(waker: WakerQueue) -> Self {
|
pub(crate) fn new(limit: usize) -> Self {
|
||||||
WorkerAvailability {
|
Self {
|
||||||
waker,
|
counter: Arc::new(AtomicUsize::new(1)),
|
||||||
available: Arc::new(AtomicBool::new(false)),
|
limit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn available(&self) -> bool {
|
/// Increment counter by 1 and return true when hitting limit
|
||||||
self.available.load(Ordering::Acquire)
|
#[inline(always)]
|
||||||
|
pub(crate) fn inc(&self) -> bool {
|
||||||
|
self.counter.fetch_add(1, Ordering::Relaxed) != self.limit
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(&self, val: bool) {
|
/// Decrement counter by 1 and return true if crossing limit.
|
||||||
let old = self.available.swap(val, Ordering::Release);
|
#[inline(always)]
|
||||||
// notify the accept on switched to available.
|
pub(crate) fn dec(&self) -> bool {
|
||||||
if !old && val {
|
self.counter.fetch_sub(1, Ordering::Relaxed) == self.limit
|
||||||
self.waker.wake(WakerInterest::WorkerAvailable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn total(&self) -> usize {
|
||||||
|
self.counter.load(Ordering::SeqCst) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct WorkerCounter {
|
||||||
|
idx: usize,
|
||||||
|
inner: Rc<(WakerQueue, Counter)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for WorkerCounter {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
idx: self.idx,
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WorkerCounter {
|
||||||
|
pub(crate) fn new(idx: usize, waker_queue: WakerQueue, counter: Counter) -> Self {
|
||||||
|
Self {
|
||||||
|
idx,
|
||||||
|
inner: Rc::new((waker_queue, counter)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn guard(&self) -> WorkerCounterGuard {
|
||||||
|
WorkerCounterGuard(self.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn total(&self) -> usize {
|
||||||
|
self.inner.1.total()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct WorkerCounterGuard(WorkerCounter);
|
||||||
|
|
||||||
|
impl Drop for WorkerCounterGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let (waker_queue, counter) = &*self.0.inner;
|
||||||
|
if counter.dec() {
|
||||||
|
waker_queue.wake(WakerInterest::WorkerAvailable(self.0.idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle to worker that can send connection message to worker and share the
|
||||||
|
/// availability of worker to other thread.
|
||||||
|
///
|
||||||
|
/// Held by [Accept](crate::accept::Accept).
|
||||||
|
pub(crate) struct WorkerHandleAccept {
|
||||||
|
idx: usize,
|
||||||
|
tx: UnboundedSender<Conn>,
|
||||||
|
counter: Counter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WorkerHandleAccept {
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn idx(&self) -> usize {
|
||||||
|
self.idx
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn send(&self, msg: Conn) -> Result<(), Conn> {
|
||||||
|
self.tx.send(msg).map_err(|msg| msg.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn inc_counter(&self) -> bool {
|
||||||
|
self.counter.inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle to worker than can send stop message to worker.
|
||||||
|
///
|
||||||
|
/// Held by [ServerBuilder](crate::builder::ServerBuilder).
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct WorkerHandleServer {
|
||||||
|
idx: usize,
|
||||||
|
tx: UnboundedSender<Stop>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WorkerHandleServer {
|
||||||
|
pub(crate) fn stop(&self, graceful: bool) -> oneshot::Receiver<bool> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
let _ = self.tx.send(Stop { graceful, tx });
|
||||||
|
rx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,14 +197,15 @@ impl WorkerAvailability {
|
|||||||
///
|
///
|
||||||
/// Worker accepts Socket objects via unbounded channel and starts stream processing.
|
/// Worker accepts Socket objects via unbounded channel and starts stream processing.
|
||||||
pub(crate) struct ServerWorker {
|
pub(crate) struct ServerWorker {
|
||||||
rx: UnboundedReceiver<WorkerCommand>,
|
// UnboundedReceiver<Conn> should always be the first field.
|
||||||
rx2: UnboundedReceiver<StopCommand>,
|
// It must be dropped as soon as ServerWorker dropping.
|
||||||
services: Vec<WorkerService>,
|
rx: UnboundedReceiver<Conn>,
|
||||||
availability: WorkerAvailability,
|
rx2: UnboundedReceiver<Stop>,
|
||||||
conns: Counter,
|
counter: WorkerCounter,
|
||||||
factories: Vec<Box<dyn InternalServiceFactory>>,
|
services: Box<[WorkerService]>,
|
||||||
|
factories: Box<[Box<dyn InternalServiceFactory>]>,
|
||||||
state: WorkerState,
|
state: WorkerState,
|
||||||
config: ServerWorkerConfig,
|
shutdown_timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WorkerService {
|
struct WorkerService {
|
||||||
@@ -163,6 +236,7 @@ enum WorkerServiceStatus {
|
|||||||
pub(crate) struct ServerWorkerConfig {
|
pub(crate) struct ServerWorkerConfig {
|
||||||
shutdown_timeout: Duration,
|
shutdown_timeout: Duration,
|
||||||
max_blocking_threads: usize,
|
max_blocking_threads: usize,
|
||||||
|
max_concurrent_connections: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ServerWorkerConfig {
|
impl Default for ServerWorkerConfig {
|
||||||
@@ -172,6 +246,7 @@ impl Default for ServerWorkerConfig {
|
|||||||
Self {
|
Self {
|
||||||
shutdown_timeout: Duration::from_secs(30),
|
shutdown_timeout: Duration::from_secs(30),
|
||||||
max_blocking_threads,
|
max_blocking_threads,
|
||||||
|
max_concurrent_connections: 25600,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,6 +256,10 @@ impl ServerWorkerConfig {
|
|||||||
self.max_blocking_threads = num;
|
self.max_blocking_threads = num;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn max_concurrent_connections(&mut self, num: usize) {
|
||||||
|
self.max_concurrent_connections = num;
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn shutdown_timeout(&mut self, dur: Duration) {
|
pub(crate) fn shutdown_timeout(&mut self, dur: Duration) {
|
||||||
self.shutdown_timeout = dur;
|
self.shutdown_timeout = dur;
|
||||||
}
|
}
|
||||||
@@ -190,13 +269,15 @@ 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,
|
waker_queue: WakerQueue,
|
||||||
config: ServerWorkerConfig,
|
config: ServerWorkerConfig,
|
||||||
) -> WorkerHandle {
|
) -> (WorkerHandleAccept, WorkerHandleServer) {
|
||||||
let (tx1, rx) = unbounded_channel();
|
let (tx1, rx) = unbounded_channel();
|
||||||
let (tx2, rx2) = unbounded_channel();
|
let (tx2, rx2) = unbounded_channel();
|
||||||
let avail = availability.clone();
|
|
||||||
|
|
||||||
|
let counter = Counter::new(config.max_concurrent_connections);
|
||||||
|
|
||||||
|
let counter_clone = counter.clone();
|
||||||
// every worker runs in it's own arbiter.
|
// every worker runs in it's own arbiter.
|
||||||
// use a custom tokio runtime builder to change the settings of runtime.
|
// use a custom tokio runtime builder to change the settings of runtime.
|
||||||
Arbiter::with_tokio_rt(move || {
|
Arbiter::with_tokio_rt(move || {
|
||||||
@@ -207,80 +288,83 @@ impl ServerWorker {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
})
|
})
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
availability.set(false);
|
let fut = factories
|
||||||
let mut wrk = MAX_CONNS_COUNTER.with(move |conns| ServerWorker {
|
|
||||||
rx,
|
|
||||||
rx2,
|
|
||||||
availability,
|
|
||||||
factories,
|
|
||||||
config,
|
|
||||||
services: Vec::new(),
|
|
||||||
conns: conns.clone(),
|
|
||||||
state: WorkerState::Unavailable,
|
|
||||||
});
|
|
||||||
|
|
||||||
let fut = wrk
|
|
||||||
.factories
|
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, factory)| {
|
.map(|(idx, factory)| {
|
||||||
let fut = factory.create();
|
let fut = factory.create();
|
||||||
async move {
|
async move { fut.await.map(|(t, s)| (idx, t, s)) }
|
||||||
fut.await.map(|r| {
|
|
||||||
r.into_iter().map(|(t, s)| (idx, t, s)).collect::<Vec<_>>()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// a second spawn to make sure worker future runs as non boxed future.
|
// a second spawn to run !Send future tasks.
|
||||||
// As Arbiter::spawn would box the future before send it to arbiter.
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let res: Result<Vec<_>, _> = join_all(fut).await.into_iter().collect();
|
let res = join_all(fut)
|
||||||
match res {
|
.await
|
||||||
Ok(services) => {
|
.into_iter()
|
||||||
for item in services {
|
.collect::<Result<Vec<_>, _>>();
|
||||||
for (factory, token, service) in item {
|
let services = match res {
|
||||||
assert_eq!(token.0, wrk.services.len());
|
Ok(res) => res
|
||||||
wrk.services.push(WorkerService {
|
.into_iter()
|
||||||
|
.fold(Vec::new(), |mut services, (factory, token, service)| {
|
||||||
|
assert_eq!(token, services.len());
|
||||||
|
services.push(WorkerService {
|
||||||
factory,
|
factory,
|
||||||
service,
|
service,
|
||||||
status: WorkerServiceStatus::Unavailable,
|
status: WorkerServiceStatus::Unavailable,
|
||||||
});
|
});
|
||||||
}
|
services
|
||||||
}
|
})
|
||||||
}
|
.into_boxed_slice(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Can not start worker: {:?}", e);
|
error!("Can not start worker: {:?}", e);
|
||||||
Arbiter::current().stop();
|
Arbiter::current().stop();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
wrk.await
|
|
||||||
|
// a third spawn to make sure ServerWorker runs as non boxed future.
|
||||||
|
spawn(ServerWorker {
|
||||||
|
rx,
|
||||||
|
rx2,
|
||||||
|
services,
|
||||||
|
counter: WorkerCounter::new(idx, waker_queue, counter_clone),
|
||||||
|
factories: factories.into_boxed_slice(),
|
||||||
|
state: Default::default(),
|
||||||
|
shutdown_timeout: config.shutdown_timeout,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
WorkerHandle::new(idx, tx1, tx2, avail)
|
handle_pair(idx, tx1, tx2, counter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restart_service(&mut self, idx: usize, factory_id: usize) {
|
||||||
|
let factory = &self.factories[factory_id];
|
||||||
|
trace!("Service {:?} failed, restarting", factory.name(idx));
|
||||||
|
self.services[idx].status = WorkerServiceStatus::Restarting;
|
||||||
|
self.state = WorkerState::Restarting(Restart {
|
||||||
|
factory_id,
|
||||||
|
token: idx,
|
||||||
|
fut: factory.create(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shutdown(&mut self, force: bool) {
|
fn shutdown(&mut self, force: bool) {
|
||||||
if force {
|
self.services
|
||||||
self.services.iter_mut().for_each(|srv| {
|
.iter_mut()
|
||||||
if srv.status == WorkerServiceStatus::Available {
|
.filter(|srv| srv.status == WorkerServiceStatus::Available)
|
||||||
srv.status = WorkerServiceStatus::Stopped;
|
.for_each(|srv| {
|
||||||
}
|
srv.status = if force {
|
||||||
});
|
WorkerServiceStatus::Stopped
|
||||||
} else {
|
} else {
|
||||||
self.services.iter_mut().for_each(move |srv| {
|
WorkerServiceStatus::Stopping
|
||||||
if srv.status == WorkerServiceStatus::Available {
|
};
|
||||||
srv.status = WorkerServiceStatus::Stopping;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn check_readiness(&mut self, cx: &mut Context<'_>) -> Result<bool, (Token, usize)> {
|
fn check_readiness(&mut self, cx: &mut Context<'_>) -> Result<bool, (usize, usize)> {
|
||||||
let mut ready = self.conns.available(cx);
|
let mut ready = true;
|
||||||
let mut failed = None;
|
|
||||||
for (idx, srv) in self.services.iter_mut().enumerate() {
|
for (idx, srv) in self.services.iter_mut().enumerate() {
|
||||||
if srv.status == WorkerServiceStatus::Available
|
if srv.status == WorkerServiceStatus::Available
|
||||||
|| srv.status == WorkerServiceStatus::Unavailable
|
|| srv.status == WorkerServiceStatus::Unavailable
|
||||||
@@ -290,7 +374,7 @@ impl ServerWorker {
|
|||||||
if srv.status == WorkerServiceStatus::Unavailable {
|
if srv.status == WorkerServiceStatus::Unavailable {
|
||||||
trace!(
|
trace!(
|
||||||
"Service {:?} is available",
|
"Service {:?} is available",
|
||||||
self.factories[srv.factory].name(Token(idx))
|
self.factories[srv.factory].name(idx)
|
||||||
);
|
);
|
||||||
srv.status = WorkerServiceStatus::Available;
|
srv.status = WorkerServiceStatus::Available;
|
||||||
}
|
}
|
||||||
@@ -301,7 +385,7 @@ impl ServerWorker {
|
|||||||
if srv.status == WorkerServiceStatus::Available {
|
if srv.status == WorkerServiceStatus::Available {
|
||||||
trace!(
|
trace!(
|
||||||
"Service {:?} is unavailable",
|
"Service {:?} is unavailable",
|
||||||
self.factories[srv.factory].name(Token(idx))
|
self.factories[srv.factory].name(idx)
|
||||||
);
|
);
|
||||||
srv.status = WorkerServiceStatus::Unavailable;
|
srv.status = WorkerServiceStatus::Unavailable;
|
||||||
}
|
}
|
||||||
@@ -309,173 +393,168 @@ impl ServerWorker {
|
|||||||
Poll::Ready(Err(_)) => {
|
Poll::Ready(Err(_)) => {
|
||||||
error!(
|
error!(
|
||||||
"Service {:?} readiness check returned error, restarting",
|
"Service {:?} readiness check returned error, restarting",
|
||||||
self.factories[srv.factory].name(Token(idx))
|
self.factories[srv.factory].name(idx)
|
||||||
);
|
);
|
||||||
failed = Some((Token(idx), srv.factory));
|
|
||||||
srv.status = WorkerServiceStatus::Failed;
|
srv.status = WorkerServiceStatus::Failed;
|
||||||
|
return Err((idx, srv.factory));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(idx) = failed {
|
|
||||||
Err(idx)
|
|
||||||
} else {
|
|
||||||
Ok(ready)
|
Ok(ready)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
enum WorkerState {
|
enum WorkerState {
|
||||||
Available,
|
Available,
|
||||||
Unavailable,
|
Unavailable,
|
||||||
Restarting(
|
Restarting(Restart),
|
||||||
usize,
|
Shutdown(Shutdown),
|
||||||
Token,
|
}
|
||||||
LocalBoxFuture<'static, Result<Vec<(Token, BoxedServerService)>, ()>>,
|
|
||||||
),
|
struct Restart {
|
||||||
Shutdown(
|
factory_id: usize,
|
||||||
Pin<Box<Sleep>>,
|
token: usize,
|
||||||
Pin<Box<Sleep>>,
|
fut: LocalBoxFuture<'static, Result<(usize, BoxedServerService), ()>>,
|
||||||
Option<oneshot::Sender<bool>>,
|
}
|
||||||
),
|
|
||||||
|
// Shutdown keep states necessary for server shutdown:
|
||||||
|
// Sleep for interval check the shutdown progress.
|
||||||
|
// Instant for the start time of shutdown.
|
||||||
|
// Sender for send back the shutdown outcome(force/grace) to StopCommand caller.
|
||||||
|
struct Shutdown {
|
||||||
|
timer: Pin<Box<Sleep>>,
|
||||||
|
start_from: Instant,
|
||||||
|
tx: oneshot::Sender<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WorkerState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Unavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ServerWorker {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Stop the Arbiter ServerWorker runs on on drop.
|
||||||
|
Arbiter::current().stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Future for ServerWorker {
|
impl Future for ServerWorker {
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
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 this = self.as_mut().get_mut();
|
||||||
|
|
||||||
// `StopWorker` message handler
|
// `StopWorker` message handler
|
||||||
if let Poll::Ready(Some(StopCommand { graceful, result })) =
|
if let Poll::Ready(Some(Stop { graceful, tx })) = Pin::new(&mut this.rx2).poll_recv(cx)
|
||||||
Pin::new(&mut self.rx2).poll_recv(cx)
|
|
||||||
{
|
{
|
||||||
self.availability.set(false);
|
let num = this.counter.total();
|
||||||
let num = num_connections();
|
|
||||||
if num == 0 {
|
if num == 0 {
|
||||||
info!("Shutting down worker, 0 connections");
|
info!("Shutting down worker, 0 connections");
|
||||||
let _ = result.send(true);
|
let _ = tx.send(true);
|
||||||
return Poll::Ready(());
|
return Poll::Ready(());
|
||||||
} else if graceful {
|
} else if graceful {
|
||||||
self.shutdown(false);
|
|
||||||
let num = num_connections();
|
|
||||||
if num != 0 {
|
|
||||||
info!("Graceful worker shutdown, {} connections", num);
|
info!("Graceful worker shutdown, {} connections", num);
|
||||||
self.state = WorkerState::Shutdown(
|
this.shutdown(false);
|
||||||
Box::pin(sleep(Duration::from_secs(1))),
|
|
||||||
Box::pin(sleep(self.config.shutdown_timeout)),
|
this.state = WorkerState::Shutdown(Shutdown {
|
||||||
Some(result),
|
timer: Box::pin(sleep(Duration::from_secs(1))),
|
||||||
);
|
start_from: Instant::now(),
|
||||||
} else {
|
tx,
|
||||||
let _ = result.send(true);
|
});
|
||||||
return Poll::Ready(());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
info!("Force shutdown worker, {} connections", num);
|
info!("Force shutdown worker, {} connections", num);
|
||||||
self.shutdown(true);
|
this.shutdown(true);
|
||||||
let _ = result.send(false);
|
|
||||||
|
let _ = tx.send(false);
|
||||||
return Poll::Ready(());
|
return Poll::Ready(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.state {
|
match this.state {
|
||||||
WorkerState::Unavailable => match self.check_readiness(cx) {
|
WorkerState::Unavailable => match this.check_readiness(cx) {
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
self.state = WorkerState::Available;
|
this.state = WorkerState::Available;
|
||||||
self.availability.set(true);
|
|
||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
Ok(false) => Poll::Pending,
|
Ok(false) => Poll::Pending,
|
||||||
Err((token, idx)) => {
|
Err((token, idx)) => {
|
||||||
trace!(
|
this.restart_service(token, idx);
|
||||||
"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)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
WorkerState::Restarting(idx, token, ref mut fut) => {
|
WorkerState::Restarting(ref mut restart) => {
|
||||||
match fut.as_mut().poll(cx) {
|
let factory_id = restart.factory_id;
|
||||||
Poll::Ready(Ok(item)) => {
|
let token = restart.token;
|
||||||
// only interest in the first item?
|
|
||||||
if let Some((token, service)) = item.into_iter().next() {
|
let (token_new, service) = ready!(restart.fut.as_mut().poll(cx))
|
||||||
trace!(
|
.unwrap_or_else(|_| {
|
||||||
"Service {:?} has been restarted",
|
|
||||||
self.factories[idx].name(token)
|
|
||||||
);
|
|
||||||
self.services[token.0].created(service);
|
|
||||||
self.state = WorkerState::Unavailable;
|
|
||||||
return self.poll(cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Poll::Ready(Err(_)) => {
|
|
||||||
panic!(
|
panic!(
|
||||||
"Can not restart {:?} service",
|
"Can not restart {:?} service",
|
||||||
self.factories[idx].name(token)
|
this.factories[factory_id].name(token)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(token, token_new);
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Service {:?} has been restarted",
|
||||||
|
this.factories[factory_id].name(token)
|
||||||
);
|
);
|
||||||
}
|
|
||||||
Poll::Pending => return Poll::Pending,
|
this.services[token].created(service);
|
||||||
}
|
this.state = WorkerState::Unavailable;
|
||||||
|
|
||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
WorkerState::Shutdown(ref mut t1, ref mut t2, ref mut tx) => {
|
WorkerState::Shutdown(ref mut shutdown) => {
|
||||||
let num = num_connections();
|
// Wait for 1 second.
|
||||||
if num == 0 {
|
ready!(shutdown.timer.as_mut().poll(cx));
|
||||||
let _ = tx.take().unwrap().send(true);
|
|
||||||
Arbiter::current().stop();
|
|
||||||
return Poll::Ready(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// check graceful timeout
|
if this.counter.total() == 0 {
|
||||||
if Pin::new(t2).poll(cx).is_ready() {
|
// Graceful shutdown.
|
||||||
let _ = tx.take().unwrap().send(false);
|
if let WorkerState::Shutdown(shutdown) = mem::take(&mut this.state) {
|
||||||
self.shutdown(true);
|
let _ = shutdown.tx.send(true);
|
||||||
Arbiter::current().stop();
|
|
||||||
return Poll::Ready(());
|
|
||||||
}
|
}
|
||||||
|
Poll::Ready(())
|
||||||
// sleep for 1 second and then check again
|
} else if shutdown.start_from.elapsed() >= this.shutdown_timeout {
|
||||||
if t1.as_mut().poll(cx).is_ready() {
|
// Timeout forceful shutdown.
|
||||||
*t1 = Box::pin(sleep(Duration::from_secs(1)));
|
if let WorkerState::Shutdown(shutdown) = mem::take(&mut this.state) {
|
||||||
let _ = t1.as_mut().poll(cx);
|
let _ = shutdown.tx.send(false);
|
||||||
|
}
|
||||||
|
Poll::Ready(())
|
||||||
|
} else {
|
||||||
|
// Reset timer and wait for 1 second.
|
||||||
|
let time = Instant::now() + Duration::from_secs(1);
|
||||||
|
shutdown.timer.as_mut().reset(time);
|
||||||
|
shutdown.timer.as_mut().poll(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
Poll::Pending
|
|
||||||
}
|
}
|
||||||
// actively poll stream and handle worker command
|
// actively poll stream and handle worker command
|
||||||
WorkerState::Available => loop {
|
WorkerState::Available => loop {
|
||||||
match self.check_readiness(cx) {
|
match this.check_readiness(cx) {
|
||||||
Ok(true) => (),
|
Ok(true) => {}
|
||||||
Ok(false) => {
|
Ok(false) => {
|
||||||
trace!("Worker is unavailable");
|
trace!("Worker is unavailable");
|
||||||
self.availability.set(false);
|
this.state = WorkerState::Unavailable;
|
||||||
self.state = WorkerState::Unavailable;
|
|
||||||
return self.poll(cx);
|
return self.poll(cx);
|
||||||
}
|
}
|
||||||
Err((token, idx)) => {
|
Err((token, idx)) => {
|
||||||
trace!(
|
this.restart_service(token, idx);
|
||||||
"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);
|
return self.poll(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match Pin::new(&mut self.rx).poll_recv(cx) {
|
|
||||||
// handle incoming io stream
|
// handle incoming io stream
|
||||||
Poll::Ready(Some(WorkerCommand(msg))) => {
|
match ready!(Pin::new(&mut this.rx).poll_recv(cx)) {
|
||||||
let guard = self.conns.get();
|
Some(msg) => {
|
||||||
let _ = self.services[msg.token.0]
|
let guard = this.counter.guard();
|
||||||
.service
|
let _ = this.services[msg.token].service.call((guard, msg.io));
|
||||||
.call((Some(guard), msg.io));
|
|
||||||
}
|
}
|
||||||
Poll::Pending => return Poll::Pending,
|
None => return Poll::Ready(()),
|
||||||
Poll::Ready(None) => return Poll::Ready(()),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::{mpsc, Arc};
|
use std::sync::{mpsc, Arc};
|
||||||
use std::{net, thread, time};
|
use std::{net, thread, time::Duration};
|
||||||
|
|
||||||
|
use actix_rt::{net::TcpStream, time::sleep};
|
||||||
use actix_server::Server;
|
use actix_server::Server;
|
||||||
use actix_service::fn_service;
|
use actix_service::fn_service;
|
||||||
use actix_utils::future::ok;
|
use actix_utils::future::ok;
|
||||||
@@ -37,7 +38,7 @@ fn test_bind() {
|
|||||||
});
|
});
|
||||||
let (_, sys) = rx.recv().unwrap();
|
let (_, sys) = rx.recv().unwrap();
|
||||||
|
|
||||||
thread::sleep(time::Duration::from_millis(500));
|
thread::sleep(Duration::from_millis(500));
|
||||||
assert!(net::TcpStream::connect(addr).is_ok());
|
assert!(net::TcpStream::connect(addr).is_ok());
|
||||||
sys.stop();
|
sys.stop();
|
||||||
let _ = h.join();
|
let _ = h.join();
|
||||||
@@ -64,7 +65,7 @@ fn test_listen() {
|
|||||||
});
|
});
|
||||||
let sys = rx.recv().unwrap();
|
let sys = rx.recv().unwrap();
|
||||||
|
|
||||||
thread::sleep(time::Duration::from_millis(500));
|
thread::sleep(Duration::from_millis(500));
|
||||||
assert!(net::TcpStream::connect(addr).is_ok());
|
assert!(net::TcpStream::connect(addr).is_ok());
|
||||||
sys.stop();
|
sys.stop();
|
||||||
let _ = h.join();
|
let _ = h.join();
|
||||||
@@ -73,11 +74,11 @@ fn test_listen() {
|
|||||||
#[test]
|
#[test]
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
fn test_start() {
|
fn test_start() {
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
use actix_codec::{BytesCodec, Framed};
|
use actix_codec::{BytesCodec, Framed};
|
||||||
use actix_rt::net::TcpStream;
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::sink::SinkExt;
|
use futures_util::sink::SinkExt;
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
let addr = unused_addr();
|
let addr = unused_addr();
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
@@ -112,16 +113,16 @@ fn test_start() {
|
|||||||
|
|
||||||
// pause
|
// pause
|
||||||
let _ = srv.pause();
|
let _ = srv.pause();
|
||||||
thread::sleep(time::Duration::from_millis(200));
|
thread::sleep(Duration::from_millis(200));
|
||||||
let mut conn = net::TcpStream::connect(addr).unwrap();
|
let mut conn = net::TcpStream::connect(addr).unwrap();
|
||||||
conn.set_read_timeout(Some(time::Duration::from_millis(100)))
|
conn.set_read_timeout(Some(Duration::from_millis(100)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let res = conn.read_exact(&mut buf);
|
let res = conn.read_exact(&mut buf);
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
|
|
||||||
// resume
|
// resume
|
||||||
let _ = srv.resume();
|
let _ = srv.resume();
|
||||||
thread::sleep(time::Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(100));
|
||||||
assert!(net::TcpStream::connect(addr).is_ok());
|
assert!(net::TcpStream::connect(addr).is_ok());
|
||||||
assert!(net::TcpStream::connect(addr).is_ok());
|
assert!(net::TcpStream::connect(addr).is_ok());
|
||||||
assert!(net::TcpStream::connect(addr).is_ok());
|
assert!(net::TcpStream::connect(addr).is_ok());
|
||||||
@@ -133,61 +134,320 @@ fn test_start() {
|
|||||||
|
|
||||||
// stop
|
// stop
|
||||||
let _ = srv.stop(false);
|
let _ = srv.stop(false);
|
||||||
thread::sleep(time::Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(100));
|
||||||
assert!(net::TcpStream::connect(addr).is_err());
|
assert!(net::TcpStream::connect(addr).is_err());
|
||||||
|
|
||||||
thread::sleep(time::Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(100));
|
||||||
sys.stop();
|
sys.stop();
|
||||||
let _ = h.join();
|
let _ = h.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[actix_rt::test]
|
||||||
fn test_configure() {
|
async fn test_max_concurrent_connections() {
|
||||||
let addr1 = unused_addr();
|
// Note:
|
||||||
let addr2 = unused_addr();
|
// A tcp listener would accept connects based on it's backlog setting.
|
||||||
let addr3 = unused_addr();
|
//
|
||||||
|
// The limit test on the other hand is only for concurrent tcp stream limiting a work
|
||||||
|
// thread accept.
|
||||||
|
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
let addr = unused_addr();
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
let num = Arc::new(AtomicUsize::new(0));
|
|
||||||
let num2 = num.clone();
|
let counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
let counter_clone = counter.clone();
|
||||||
|
|
||||||
|
let max_conn = 3;
|
||||||
|
|
||||||
let h = thread::spawn(move || {
|
let h = thread::spawn(move || {
|
||||||
let num = num2.clone();
|
actix_rt::System::new().block_on(async {
|
||||||
let sys = actix_rt::System::new();
|
let server = Server::build()
|
||||||
let srv = sys.block_on(lazy(|_| {
|
// Set a relative higher backlog.
|
||||||
Server::build()
|
.backlog(12)
|
||||||
|
// max connection for a worker is 3.
|
||||||
|
.maxconn(max_conn)
|
||||||
|
.workers(1)
|
||||||
.disable_signals()
|
.disable_signals()
|
||||||
.configure(move |cfg| {
|
.bind("test", addr, move || {
|
||||||
|
let counter = counter.clone();
|
||||||
|
fn_service(move |_io: TcpStream| {
|
||||||
|
let counter = counter.clone();
|
||||||
|
async move {
|
||||||
|
counter.fetch_add(1, Ordering::SeqCst);
|
||||||
|
sleep(Duration::from_secs(20)).await;
|
||||||
|
counter.fetch_sub(1, Ordering::SeqCst);
|
||||||
|
Ok::<(), ()>(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.run();
|
||||||
|
|
||||||
|
let _ = tx.send((server.clone(), actix_rt::System::current()));
|
||||||
|
|
||||||
|
server.await
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let (srv, sys) = rx.recv().unwrap();
|
||||||
|
|
||||||
|
let mut conns = vec![];
|
||||||
|
|
||||||
|
for _ in 0..12 {
|
||||||
|
let conn = tokio::net::TcpStream::connect(addr).await.unwrap();
|
||||||
|
conns.push(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(Duration::from_secs(5)).await;
|
||||||
|
|
||||||
|
// counter would remain at 3 even with 12 successful connection.
|
||||||
|
// and 9 of them remain in backlog.
|
||||||
|
assert_eq!(max_conn, counter_clone.load(Ordering::SeqCst));
|
||||||
|
|
||||||
|
for mut conn in conns {
|
||||||
|
conn.shutdown().await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.stop(false).await;
|
||||||
|
|
||||||
|
sys.stop();
|
||||||
|
let _ = h.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_service_restart() {
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use actix_service::{fn_factory, Service};
|
||||||
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
struct TestService(Arc<AtomicUsize>);
|
||||||
|
|
||||||
|
impl Service<TcpStream> for TestService {
|
||||||
|
type Response = ();
|
||||||
|
type Error = ();
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
fn poll_ready(&self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
let TestService(ref counter) = self;
|
||||||
|
let c = counter.fetch_add(1, Ordering::SeqCst);
|
||||||
|
// Force the service to restart on first readiness check.
|
||||||
|
if c > 0 {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
} else {
|
||||||
|
Poll::Ready(Err(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&self, _: TcpStream) -> Self::Future {
|
||||||
|
Box::pin(async { Ok(()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let addr1 = unused_addr();
|
||||||
|
let addr2 = unused_addr();
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let num = Arc::new(AtomicUsize::new(0));
|
||||||
|
let num2 = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
let num_clone = num.clone();
|
||||||
|
let num2_clone = num2.clone();
|
||||||
|
|
||||||
|
let h = thread::spawn(move || {
|
||||||
let num = num.clone();
|
let num = num.clone();
|
||||||
let lst = net::TcpListener::bind(addr3).unwrap();
|
actix_rt::System::new().block_on(async {
|
||||||
cfg.bind("addr1", addr1)
|
let server = Server::build()
|
||||||
.unwrap()
|
.backlog(1)
|
||||||
.bind("addr2", addr2)
|
.disable_signals()
|
||||||
.unwrap()
|
.bind("addr1", addr1, move || {
|
||||||
.listen("addr3", lst)
|
|
||||||
.apply(move |rt| {
|
|
||||||
let num = num.clone();
|
let num = num.clone();
|
||||||
rt.service("addr1", fn_service(|_| ok::<_, ()>(())));
|
fn_factory(move || {
|
||||||
rt.service("addr3", fn_service(|_| ok::<_, ()>(())));
|
let num = num.clone();
|
||||||
rt.on_start(lazy(move |_| {
|
async move { Ok::<_, ()>(TestService(num)) }
|
||||||
let _ = num.fetch_add(1, Relaxed);
|
})
|
||||||
}))
|
})
|
||||||
|
.unwrap()
|
||||||
|
.bind("addr2", addr2, move || {
|
||||||
|
let num2 = num2.clone();
|
||||||
|
fn_factory(move || {
|
||||||
|
let num2 = num2.clone();
|
||||||
|
async move { Ok::<_, ()>(TestService(num2)) }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.workers(1)
|
.workers(1)
|
||||||
.run()
|
.run();
|
||||||
}));
|
|
||||||
|
|
||||||
let _ = tx.send((srv, actix_rt::System::current()));
|
let _ = tx.send((server.clone(), actix_rt::System::current()));
|
||||||
let _ = sys.run();
|
server.await
|
||||||
|
})
|
||||||
});
|
});
|
||||||
let (_, sys) = rx.recv().unwrap();
|
|
||||||
thread::sleep(time::Duration::from_millis(500));
|
|
||||||
|
|
||||||
assert!(net::TcpStream::connect(addr1).is_ok());
|
let (server, sys) = rx.recv().unwrap();
|
||||||
assert!(net::TcpStream::connect(addr2).is_ok());
|
|
||||||
assert!(net::TcpStream::connect(addr3).is_ok());
|
for _ in 0..5 {
|
||||||
assert_eq!(num.load(Relaxed), 1);
|
TcpStream::connect(addr1)
|
||||||
sys.stop();
|
.await
|
||||||
let _ = h.join();
|
.unwrap()
|
||||||
|
.shutdown()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
TcpStream::connect(addr2)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.shutdown()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(Duration::from_secs(3)).await;
|
||||||
|
|
||||||
|
assert!(num_clone.load(Ordering::SeqCst) > 5);
|
||||||
|
assert!(num2_clone.load(Ordering::SeqCst) > 5);
|
||||||
|
|
||||||
|
sys.stop();
|
||||||
|
let _ = server.stop(false);
|
||||||
|
let _ = h.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ignore]
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn worker_restart() {
|
||||||
|
use actix_service::{Service, ServiceFactory};
|
||||||
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
|
struct TestServiceFactory(Arc<AtomicUsize>);
|
||||||
|
|
||||||
|
impl ServiceFactory<TcpStream> for TestServiceFactory {
|
||||||
|
type Response = ();
|
||||||
|
type Error = ();
|
||||||
|
type Config = ();
|
||||||
|
type Service = TestService;
|
||||||
|
type InitError = ();
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
|
fn new_service(&self, _: Self::Config) -> Self::Future {
|
||||||
|
let counter = self.0.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
|
Box::pin(async move { Ok(TestService(counter)) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestService(usize);
|
||||||
|
|
||||||
|
impl Service<TcpStream> for TestService {
|
||||||
|
type Response = ();
|
||||||
|
type Error = ();
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
actix_service::always_ready!();
|
||||||
|
|
||||||
|
fn call(&self, stream: TcpStream) -> Self::Future {
|
||||||
|
let counter = self.0;
|
||||||
|
|
||||||
|
let mut stream = stream.into_std().unwrap();
|
||||||
|
use std::io::Write;
|
||||||
|
let str = counter.to_string();
|
||||||
|
let buf = str.as_bytes();
|
||||||
|
|
||||||
|
let mut written = 0;
|
||||||
|
|
||||||
|
while written < buf.len() {
|
||||||
|
if let Ok(n) = stream.write(&buf[written..]) {
|
||||||
|
written += n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.flush().unwrap();
|
||||||
|
stream.shutdown(net::Shutdown::Write).unwrap();
|
||||||
|
|
||||||
|
// force worker 2 to restart service once.
|
||||||
|
if counter == 2 {
|
||||||
|
panic!("panic on purpose")
|
||||||
|
} else {
|
||||||
|
Box::pin(async { Ok(()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let addr = unused_addr();
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
let counter = Arc::new(AtomicUsize::new(1));
|
||||||
|
let h = thread::spawn(move || {
|
||||||
|
let counter = counter.clone();
|
||||||
|
actix_rt::System::new().block_on(async {
|
||||||
|
let server = Server::build()
|
||||||
|
.disable_signals()
|
||||||
|
.bind("addr", addr, move || TestServiceFactory(counter.clone()))
|
||||||
|
.unwrap()
|
||||||
|
.workers(2)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
let _ = tx.send((server.clone(), actix_rt::System::current()));
|
||||||
|
server.await
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let (server, sys) = rx.recv().unwrap();
|
||||||
|
|
||||||
|
sleep(Duration::from_secs(3)).await;
|
||||||
|
|
||||||
|
let mut buf = [0; 8];
|
||||||
|
|
||||||
|
// worker 1 would not restart and return it's id consistently.
|
||||||
|
let mut stream = TcpStream::connect(addr).await.unwrap();
|
||||||
|
let n = stream.read(&mut buf).await.unwrap();
|
||||||
|
let id = String::from_utf8_lossy(&buf[0..n]);
|
||||||
|
assert_eq!("1", id);
|
||||||
|
stream.shutdown().await.unwrap();
|
||||||
|
|
||||||
|
// worker 2 dead after return response.
|
||||||
|
let mut stream = TcpStream::connect(addr).await.unwrap();
|
||||||
|
let n = stream.read(&mut buf).await.unwrap();
|
||||||
|
let id = String::from_utf8_lossy(&buf[0..n]);
|
||||||
|
assert_eq!("2", id);
|
||||||
|
stream.shutdown().await.unwrap();
|
||||||
|
|
||||||
|
// request to worker 1
|
||||||
|
let mut stream = TcpStream::connect(addr).await.unwrap();
|
||||||
|
let n = stream.read(&mut buf).await.unwrap();
|
||||||
|
let id = String::from_utf8_lossy(&buf[0..n]);
|
||||||
|
assert_eq!("1", id);
|
||||||
|
stream.shutdown().await.unwrap();
|
||||||
|
|
||||||
|
// TODO: Remove sleep if it can pass CI.
|
||||||
|
sleep(Duration::from_secs(3)).await;
|
||||||
|
|
||||||
|
// worker 2 restarting and work goes to worker 1.
|
||||||
|
let mut stream = TcpStream::connect(addr).await.unwrap();
|
||||||
|
let n = stream.read(&mut buf).await.unwrap();
|
||||||
|
let id = String::from_utf8_lossy(&buf[0..n]);
|
||||||
|
assert_eq!("1", id);
|
||||||
|
stream.shutdown().await.unwrap();
|
||||||
|
|
||||||
|
// TODO: Remove sleep if it can pass CI.
|
||||||
|
sleep(Duration::from_secs(3)).await;
|
||||||
|
|
||||||
|
// worker 2 restarted but worker 1 was still the next to accept connection.
|
||||||
|
let mut stream = TcpStream::connect(addr).await.unwrap();
|
||||||
|
let n = stream.read(&mut buf).await.unwrap();
|
||||||
|
let id = String::from_utf8_lossy(&buf[0..n]);
|
||||||
|
assert_eq!("1", id);
|
||||||
|
stream.shutdown().await.unwrap();
|
||||||
|
|
||||||
|
// TODO: Remove sleep if it can pass CI.
|
||||||
|
sleep(Duration::from_secs(3)).await;
|
||||||
|
|
||||||
|
// worker 2 accept connection again but it's id is 3.
|
||||||
|
let mut stream = TcpStream::connect(addr).await.unwrap();
|
||||||
|
let n = stream.read(&mut buf).await.unwrap();
|
||||||
|
let id = String::from_utf8_lossy(&buf[0..n]);
|
||||||
|
assert_eq!("3", id);
|
||||||
|
stream.shutdown().await.unwrap();
|
||||||
|
|
||||||
|
sys.stop();
|
||||||
|
let _ = server.stop(false);
|
||||||
|
let _ = h.join().unwrap();
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,12 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 2.0.0 - 2021-04-16
|
||||||
|
* Removed pipeline and related structs/functions. [#335]
|
||||||
|
|
||||||
|
[#335]: https://github.com/actix/actix-net/pull/335
|
||||||
|
|
||||||
|
|
||||||
## 2.0.0-beta.5 - 2021-03-15
|
## 2.0.0-beta.5 - 2021-03-15
|
||||||
* Add default `Service` trait impl for `Rc<S: Service>` and `&S: Service`. [#288]
|
* Add default `Service` trait impl for `Rc<S: Service>` and `&S: Service`. [#288]
|
||||||
* Add `boxed::rc_service` function for constructing `boxed::RcService` type [#290]
|
* Add `boxed::rc_service` function for constructing `boxed::RcService` type [#290]
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-service"
|
name = "actix-service"
|
||||||
version = "2.0.0-beta.5"
|
version = "2.0.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
@@ -8,11 +8,8 @@ authors = [
|
|||||||
]
|
]
|
||||||
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"
|
|
||||||
repository = "https://github.com/actix/actix-net.git"
|
|
||||||
documentation = "https://docs.rs/actix-service"
|
|
||||||
readme = "README.md"
|
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
|
repository = "https://github.com/actix/actix-net"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@@ -22,8 +19,10 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures-core = { version = "0.3.7", default-features = false }
|
futures-core = { version = "0.3.7", default-features = false }
|
||||||
|
paste = "1"
|
||||||
pin-project-lite = "0.2"
|
pin-project-lite = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.0.0"
|
actix-rt = "2.0.0"
|
||||||
|
actix-utils = "3.0.0"
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
futures-util = { version = "0.3.7", default-features = false }
|
||||||
|
@@ -3,10 +3,10 @@
|
|||||||
> Service trait and combinators for representing asynchronous request/response operations.
|
> Service trait and combinators for representing asynchronous request/response operations.
|
||||||
|
|
||||||
[](https://crates.io/crates/actix-service)
|
[](https://crates.io/crates/actix-service)
|
||||||
[](https://docs.rs/actix-service/2.0.0-beta.5)
|
[](https://docs.rs/actix-service/2.0.0)
|
||||||
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
[](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html)
|
||||||

|

|
||||||
[](https://deps.rs/crate/actix-service/2.0.0-beta.5)
|
[](https://deps.rs/crate/actix-service/2.0.0)
|
||||||

|

|
||||||
[](https://discord.gg/NWpN5mmg3x)
|
[](https://discord.gg/NWpN5mmg3x)
|
||||||
|
|
||||||
|
@@ -11,11 +11,11 @@ use pin_project_lite::pin_project;
|
|||||||
|
|
||||||
use super::{Service, ServiceFactory};
|
use super::{Service, ServiceFactory};
|
||||||
|
|
||||||
/// Service for the `and_then` combinator, chaining a computation onto the end
|
/// Service for the `and_then` combinator, chaining a computation onto the end of another service
|
||||||
/// of another service which completes successfully.
|
/// 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 struct AndThenService<A, B, Req>(Rc<(A, B)>, PhantomData<Req>);
|
||||||
|
|
||||||
impl<A, B, Req> AndThenService<A, B, Req> {
|
impl<A, B, Req> AndThenService<A, B, Req> {
|
||||||
/// Create new `AndThen` combinator
|
/// Create new `AndThen` combinator
|
||||||
@@ -64,7 +64,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
pub(crate) struct AndThenServiceResponse<A, B, Req>
|
pub struct AndThenServiceResponse<A, B, Req>
|
||||||
where
|
where
|
||||||
A: Service<Req>,
|
A: Service<Req>,
|
||||||
B: Service<A::Response, Error = A::Error>,
|
B: Service<A::Response, Error = A::Error>,
|
||||||
@@ -117,7 +117,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// `.and_then()` service factory combinator
|
/// `.and_then()` service factory combinator
|
||||||
pub(crate) struct AndThenServiceFactory<A, B, Req>
|
pub struct AndThenServiceFactory<A, B, Req>
|
||||||
where
|
where
|
||||||
A: ServiceFactory<Req>,
|
A: ServiceFactory<Req>,
|
||||||
A::Config: Clone,
|
A::Config: Clone,
|
||||||
@@ -200,7 +200,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pin_project! {
|
pin_project! {
|
||||||
pub(crate) struct AndThenServiceFactoryResponse<A, B, Req>
|
pub struct AndThenServiceFactoryResponse<A, B, Req>
|
||||||
where
|
where
|
||||||
A: ServiceFactory<Req>,
|
A: ServiceFactory<Req>,
|
||||||
B: ServiceFactory<A::Response>,
|
B: ServiceFactory<A::Response>,
|
||||||
@@ -272,7 +272,9 @@ mod tests {
|
|||||||
use futures_util::future::lazy;
|
use futures_util::future::lazy;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fn_factory, ok, pipeline, pipeline_factory, ready, Ready, Service, ServiceFactory,
|
fn_factory, ok,
|
||||||
|
pipeline::{pipeline, pipeline_factory},
|
||||||
|
ready, Ready, Service, ServiceFactory,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Srv1(Rc<Cell<usize>>);
|
struct Srv1(Rc<Cell<usize>>);
|
||||||
|
@@ -214,7 +214,11 @@ mod tests {
|
|||||||
use futures_util::future::lazy;
|
use futures_util::future::lazy;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{ok, pipeline, pipeline_factory, Ready, Service, ServiceFactory};
|
use crate::{
|
||||||
|
ok,
|
||||||
|
pipeline::{pipeline, pipeline_factory},
|
||||||
|
Ready, Service, ServiceFactory,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Srv;
|
struct Srv;
|
||||||
|
@@ -3,19 +3,22 @@
|
|||||||
use alloc::{boxed::Box, rc::Rc};
|
use alloc::{boxed::Box, rc::Rc};
|
||||||
use core::{future::Future, pin::Pin};
|
use core::{future::Future, pin::Pin};
|
||||||
|
|
||||||
|
use paste::paste;
|
||||||
|
|
||||||
use crate::{Service, ServiceFactory};
|
use crate::{Service, ServiceFactory};
|
||||||
|
|
||||||
/// A boxed future without a Send bound or lifetime parameters.
|
/// A boxed future with no send bound or lifetime parameters.
|
||||||
pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||||
|
|
||||||
macro_rules! service_object {
|
macro_rules! service_object {
|
||||||
($name: ident, $type: tt, $fn_name: ident) => {
|
($name: ident, $type: tt, $fn_name: ident) => {
|
||||||
/// Type alias for service trait object.
|
paste! {
|
||||||
|
#[doc = "Type alias for service trait object using `" $type "`."]
|
||||||
pub type $name<Req, Res, Err> = $type<
|
pub type $name<Req, Res, Err> = $type<
|
||||||
dyn Service<Req, Response = Res, Error = Err, Future = BoxFuture<Result<Res, Err>>>,
|
dyn Service<Req, Response = Res, Error = Err, Future = BoxFuture<Result<Res, Err>>>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/// Create service trait object.
|
#[doc = "Wraps service as a trait object using [`" $name "`]."]
|
||||||
pub fn $fn_name<S, Req>(service: S) -> $name<Req, S::Response, S::Error>
|
pub fn $fn_name<S, Req>(service: S) -> $name<Req, S::Response, S::Error>
|
||||||
where
|
where
|
||||||
S: Service<Req> + 'static,
|
S: Service<Req> + 'static,
|
||||||
@@ -24,6 +27,7 @@ macro_rules! service_object {
|
|||||||
{
|
{
|
||||||
$type::new(ServiceWrapper::new(service))
|
$type::new(ServiceWrapper::new(service))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,10 +60,10 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper for a service factory trait object that will produce a boxed trait object service.
|
/// Wrapper for a service factory that will map it's services to boxed trait object services.
|
||||||
pub struct BoxServiceFactory<Cfg, Req, Res, Err, InitErr>(Inner<Cfg, Req, Res, Err, InitErr>);
|
pub struct BoxServiceFactory<Cfg, Req, Res, Err, InitErr>(Inner<Cfg, Req, Res, Err, InitErr>);
|
||||||
|
|
||||||
/// Create service factory trait object.
|
/// Wraps a service factory that returns service trait objects.
|
||||||
pub fn factory<SF, Req>(
|
pub fn factory<SF, Req>(
|
||||||
factory: SF,
|
factory: SF,
|
||||||
) -> BoxServiceFactory<SF::Config, Req, SF::Response, SF::Error, SF::InitError>
|
) -> BoxServiceFactory<SF::Config, Req, SF::Response, SF::Error, SF::InitError>
|
||||||
|
@@ -1,8 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
map::Map, map_err::MapErr, transform_err::TransformMapInitErr, Service, ServiceFactory,
|
and_then::{AndThenService, AndThenServiceFactory},
|
||||||
Transform,
|
map::Map,
|
||||||
|
map_err::MapErr,
|
||||||
|
transform_err::TransformMapInitErr,
|
||||||
|
IntoService, IntoServiceFactory, Service, ServiceFactory, Transform,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// An extension trait for [`Service`]s that provides a variety of convenient adapters.
|
||||||
pub trait ServiceExt<Req>: Service<Req> {
|
pub trait ServiceExt<Req>: Service<Req> {
|
||||||
/// 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.
|
||||||
@@ -36,10 +40,27 @@ pub trait ServiceExt<Req>: Service<Req> {
|
|||||||
{
|
{
|
||||||
MapErr::new(self, f)
|
MapErr::new(self, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call another service after call to this one has resolved successfully.
|
||||||
|
///
|
||||||
|
/// This function can be used to chain two services together and ensure that the second service
|
||||||
|
/// isn't called until call to the fist service have finished. Result of the call to the first
|
||||||
|
/// service is used as an input parameter for the second service's call.
|
||||||
|
///
|
||||||
|
/// Note that this function consumes the receiving service and returns a wrapped version of it.
|
||||||
|
fn and_then<I, S1>(self, service: I) -> AndThenService<Self, S1, Req>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
I: IntoService<S1, Self::Response>,
|
||||||
|
S1: Service<Self::Response, Error = Self::Error>,
|
||||||
|
{
|
||||||
|
AndThenService::new(self, service.into_service())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, Req> ServiceExt<Req> for S where S: Service<Req> {}
|
impl<S, Req> ServiceExt<Req> for S where S: Service<Req> {}
|
||||||
|
|
||||||
|
/// An extension trait for [`ServiceFactory`]s that provides a variety of convenient adapters.
|
||||||
pub trait ServiceFactoryExt<Req>: ServiceFactory<Req> {
|
pub trait ServiceFactoryExt<Req>: ServiceFactory<Req> {
|
||||||
/// 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.
|
||||||
@@ -68,10 +89,27 @@ pub trait ServiceFactoryExt<Req>: ServiceFactory<Req> {
|
|||||||
{
|
{
|
||||||
crate::map_init_err::MapInitErr::new(self, f)
|
crate::map_init_err::MapInitErr::new(self, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call another service after call to this one has resolved successfully.
|
||||||
|
fn and_then<I, SF1>(self, factory: I) -> AndThenServiceFactory<Self, SF1, Req>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
Self::Config: Clone,
|
||||||
|
I: IntoServiceFactory<SF1, Self::Response>,
|
||||||
|
SF1: ServiceFactory<
|
||||||
|
Self::Response,
|
||||||
|
Config = Self::Config,
|
||||||
|
Error = Self::Error,
|
||||||
|
InitError = Self::InitError,
|
||||||
|
>,
|
||||||
|
{
|
||||||
|
AndThenServiceFactory::new(self, factory.into_factory())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<SF, Req> ServiceFactoryExt<Req> for SF where SF: ServiceFactory<Req> {}
|
impl<SF, Req> ServiceFactoryExt<Req> for SF where SF: ServiceFactory<Req> {}
|
||||||
|
|
||||||
|
/// An extension trait for [`Transform`]s that provides a variety of convenient adapters.
|
||||||
pub trait TransformExt<S, Req>: Transform<S, Req> {
|
pub trait TransformExt<S, Req>: Transform<S, Req> {
|
||||||
/// Return a new `Transform` whose init error is mapped to to a different type.
|
/// 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>
|
fn map_init_err<F, E>(self, f: F) -> TransformMapInitErr<Self, S, Req, F, E>
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
//! See [`Service`] docs for information on this crate's foundational trait.
|
//! See [`Service`] docs for information on this crate's foundational trait.
|
||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
#![deny(rust_2018_idioms, nonstandard_style, future_incompatible)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
#![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")]
|
||||||
@@ -37,7 +38,6 @@ pub use self::apply_cfg::{apply_cfg, apply_cfg_factory};
|
|||||||
pub use self::ext::{ServiceExt, ServiceFactoryExt, TransformExt};
|
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::transform::{apply, ApplyTransform, Transform};
|
pub use self::transform::{apply, ApplyTransform, Transform};
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
@@ -53,8 +53,14 @@ use self::ready::{err, ok, ready, Ready};
|
|||||||
/// async fn(Request) -> Result<Response, Err>
|
/// async fn(Request) -> Result<Response, Err>
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The `Service` trait just generalizes this form where each parameter is described as an
|
/// The `Service` trait just generalizes this form. Requests are defined as a generic type parameter
|
||||||
/// associated type on the trait. Services can also have mutable state that influence computation.
|
/// and responses and other details are defined as associated types on the trait impl. Notice that
|
||||||
|
/// this design means that services can receive many request types and converge them to a single
|
||||||
|
/// response type.
|
||||||
|
///
|
||||||
|
/// Services can also have mutable state that influence computation by using a `Cell`, `RefCell`
|
||||||
|
/// or `Mutex`. Services intentionally do not take `&mut self` to reduce overhead in the
|
||||||
|
/// common cases.
|
||||||
///
|
///
|
||||||
/// `Service` provides a symmetric and uniform API; the same abstractions can be used to represent
|
/// `Service` provides a symmetric and uniform API; the same abstractions can be used to represent
|
||||||
/// both clients and servers. Services describe only _transformation_ operations which encourage
|
/// both clients and servers. Services describe only _transformation_ operations which encourage
|
||||||
@@ -64,11 +70,10 @@ use self::ready::{err, ok, ready, Ready};
|
|||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// struct MyService;
|
/// struct MyService;
|
||||||
///
|
///
|
||||||
/// impl Service for MyService {
|
/// impl Service<u8> for MyService {
|
||||||
/// type Request = u8;
|
|
||||||
/// type Response = u64;
|
/// type Response = u64;
|
||||||
/// type Error = MyError;
|
/// type Error = MyError;
|
||||||
/// type Future = Pin<Box<Future<Output=Result<Self::Response, Self::Error>>>>;
|
/// type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||||
///
|
///
|
||||||
/// fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { ... }
|
/// fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { ... }
|
||||||
///
|
///
|
||||||
@@ -77,10 +82,13 @@ use self::ready::{err, ok, ready, Ready};
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// 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
|
/// ```ignore
|
||||||
/// async fn my_service(req: u8) -> Result<u64, MyError>;
|
/// async fn my_service(req: u8) -> Result<u64, MyError>;
|
||||||
|
///
|
||||||
|
/// let svc = fn_service(my_service)
|
||||||
|
/// svc.call(123)
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Service<Req> {
|
pub trait Service<Req> {
|
||||||
/// Responses given by the service.
|
/// Responses given by the service.
|
||||||
@@ -94,13 +102,12 @@ pub trait Service<Req> {
|
|||||||
|
|
||||||
/// Returns `Ready` when the service is able to process requests.
|
/// Returns `Ready` when the service is able to process requests.
|
||||||
///
|
///
|
||||||
/// If the service is at capacity, then `Pending` is returned and the task
|
/// If the service is at capacity, then `Pending` is returned and the task is notified when the
|
||||||
/// is notified when the service becomes ready again. This function is
|
/// service becomes ready again. This function is expected to be called while on a task.
|
||||||
/// expected to be called while on a task.
|
|
||||||
///
|
///
|
||||||
/// This is a **best effort** implementation. False positives are permitted.
|
/// This is a best effort implementation. False positives are permitted. It is permitted for
|
||||||
/// It is permitted for the service to return `Ready` from a `poll_ready`
|
/// the service to return `Ready` from a `poll_ready` call and the next invocation of `call`
|
||||||
/// call and the next invocation of `call` results in an error.
|
/// 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 a different task to `call`.
|
||||||
@@ -109,25 +116,26 @@ pub trait Service<Req> {
|
|||||||
|
|
||||||
/// Process the request and return the response asynchronously.
|
/// Process the request and return the response asynchronously.
|
||||||
///
|
///
|
||||||
/// This function is expected to be callable off task. As such,
|
/// This function is expected to be callable off-task. As such, implementations of `call` should
|
||||||
/// implementations should take care to not call `poll_ready`. If the
|
/// take care to not call `poll_ready`. If the service is at capacity and the request is unable
|
||||||
/// service is at capacity and the request is unable to be handled, the
|
/// to be handled, the returned `Future` should resolve to an error.
|
||||||
/// returned `Future` should resolve to an error.
|
|
||||||
///
|
///
|
||||||
/// Calling `call` without calling `poll_ready` is permitted. The
|
/// Invoking `call` without first invoking `poll_ready` is permitted. Implementations must be
|
||||||
/// implementation must be resilient to this fact.
|
/// resilient to this fact.
|
||||||
fn call(&self, req: Req) -> Self::Future;
|
fn call(&self, req: Req) -> Self::Future;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Factory for creating `Service`s.
|
/// Factory for creating `Service`s.
|
||||||
///
|
///
|
||||||
/// Acts as a service factory. This is useful for cases where new `Service`s
|
/// This is useful for cases where new `Service`s must be produced. One case is a TCP
|
||||||
/// must be produced. One case is a TCP server listener. The listener
|
/// server listener: a listener accepts new connections, constructs a new `Service` for each using
|
||||||
/// accepts new TCP streams, obtains a new `Service` using the
|
/// the `ServiceFactory` trait, and uses the new `Service` to process inbound requests on that new
|
||||||
/// `ServiceFactory` trait, and uses the new `Service` to process inbound
|
/// connection.
|
||||||
/// requests on that new TCP stream.
|
|
||||||
///
|
///
|
||||||
/// `Config` is a service factory configuration type.
|
/// `Config` is a service factory configuration type.
|
||||||
|
///
|
||||||
|
/// Simple factories may be able to use [`fn_factory`] or [`fn_factory_with_config`] to
|
||||||
|
/// reduce boilerplate.
|
||||||
pub trait ServiceFactory<Req> {
|
pub trait ServiceFactory<Req> {
|
||||||
/// Responses given by the created services.
|
/// Responses given by the created services.
|
||||||
type Response;
|
type Response;
|
||||||
@@ -144,7 +152,7 @@ pub trait ServiceFactory<Req> {
|
|||||||
/// Errors potentially raised while building a service.
|
/// Errors potentially raised while building a service.
|
||||||
type InitError;
|
type InitError;
|
||||||
|
|
||||||
/// The future of the `Service` instance.
|
/// The future of the `Service` instance.g
|
||||||
type Future: Future<Output = Result<Self::Service, Self::InitError>>;
|
type Future: Future<Output = Result<Self::Service, Self::InitError>>;
|
||||||
|
|
||||||
/// Create and return a new service asynchronously.
|
/// Create and return a new service asynchronously.
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
/// A boilerplate implementation of [`Service::poll_ready`] that always signals readiness.
|
/// An implementation of [`poll_ready`]() that always signals readiness.
|
||||||
///
|
///
|
||||||
/// [`Service::poll_ready`]: crate::Service::poll_ready
|
/// This should only be used for basic leaf services that have no concept of un-readiness.
|
||||||
|
/// For wrapper or other serivice types, use [`forward_ready!`] for simple cases or write a bespoke
|
||||||
|
/// `poll_ready` implementation.
|
||||||
|
///
|
||||||
|
/// [`poll_ready`]: crate::Service::poll_ready
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
@@ -34,12 +38,12 @@ macro_rules! always_ready {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A boilerplate implementation of [`Service::poll_ready`] that forwards readiness checks to a
|
/// An implementation of [`poll_ready`] that forwards readiness checks to a
|
||||||
/// named struct field.
|
/// named struct field.
|
||||||
///
|
///
|
||||||
/// Tuple structs are not supported.
|
/// Tuple structs are not supported.
|
||||||
///
|
///
|
||||||
/// [`Service::poll_ready`]: crate::Service::poll_ready
|
/// [`poll_ready`]: crate::Service::poll_ready
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
|
@@ -1,3 +1,6 @@
|
|||||||
|
// TODO: see if pipeline is necessary
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use core::{
|
use core::{
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
@@ -11,7 +14,7 @@ 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(crate) fn pipeline<I, S, Req>(service: I) -> Pipeline<S, Req>
|
||||||
where
|
where
|
||||||
I: IntoService<S, Req>,
|
I: IntoService<S, Req>,
|
||||||
S: Service<Req>,
|
S: Service<Req>,
|
||||||
@@ -23,7 +26,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 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(crate) fn pipeline_factory<I, SF, Req>(factory: I) -> PipelineFactory<SF, Req>
|
||||||
where
|
where
|
||||||
I: IntoServiceFactory<SF, Req>,
|
I: IntoServiceFactory<SF, Req>,
|
||||||
SF: ServiceFactory<Req>,
|
SF: ServiceFactory<Req>,
|
||||||
@@ -35,7 +38,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 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(crate) struct Pipeline<S, Req> {
|
||||||
service: S,
|
service: S,
|
||||||
_phantom: PhantomData<Req>,
|
_phantom: PhantomData<Req>,
|
||||||
}
|
}
|
||||||
@@ -157,7 +160,7 @@ impl<S: Service<Req>, Req> Service<Req> for Pipeline<S, Req> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Pipeline factory
|
/// Pipeline factory
|
||||||
pub struct PipelineFactory<SF, Req> {
|
pub(crate) struct PipelineFactory<SF, Req> {
|
||||||
factory: SF,
|
factory: SF,
|
||||||
_phantom: PhantomData<Req>,
|
_phantom: PhantomData<Req>,
|
||||||
}
|
}
|
||||||
|
@@ -246,7 +246,11 @@ mod tests {
|
|||||||
|
|
||||||
use futures_util::future::lazy;
|
use futures_util::future::lazy;
|
||||||
|
|
||||||
use crate::{err, ok, pipeline, pipeline_factory, ready, Ready, Service, ServiceFactory};
|
use crate::{
|
||||||
|
err, ok,
|
||||||
|
pipeline::{pipeline, pipeline_factory},
|
||||||
|
ready, Ready, Service, ServiceFactory,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Srv1(Rc<Cell<usize>>);
|
struct Srv1(Rc<Cell<usize>>);
|
||||||
|
@@ -21,13 +21,12 @@ where
|
|||||||
ApplyTransform::new(t, factory.into_factory())
|
ApplyTransform::new(t, factory.into_factory())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `Transform` trait defines the interface of a service factory that wraps inner service
|
/// 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
|
/// Transformers wrap an inner service and runs during inbound and/or outbound processing in the
|
||||||
/// the request/response lifecycle. It may modify request and/or response.
|
/// service lifecycle. It may modify request and/or response.
|
||||||
///
|
///
|
||||||
/// For example, timeout transform:
|
/// For example, a timeout service wrapper:
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// pub struct Timeout<S> {
|
/// pub struct Timeout<S> {
|
||||||
@@ -35,18 +34,14 @@ where
|
|||||||
/// timeout: Duration,
|
/// timeout: Duration,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl<S> Service for Timeout<S>
|
/// impl<S: Service<Req>, Req> Service<Req> for Timeout<S> {
|
||||||
/// where
|
|
||||||
/// S: Service,
|
|
||||||
/// {
|
|
||||||
/// type Request = S::Request;
|
|
||||||
/// type Response = S::Response;
|
/// type Response = S::Response;
|
||||||
/// type Error = TimeoutError<S::Error>;
|
/// type Error = TimeoutError<S::Error>;
|
||||||
/// type Future = TimeoutServiceResponse<S>;
|
/// type Future = TimeoutServiceResponse<S>;
|
||||||
///
|
///
|
||||||
/// actix_service::forward_ready!(service);
|
/// actix_service::forward_ready!(service);
|
||||||
///
|
///
|
||||||
/// fn call(&self, req: S::Request) -> Self::Future {
|
/// fn call(&self, req: Req) -> Self::Future {
|
||||||
/// TimeoutServiceResponse {
|
/// TimeoutServiceResponse {
|
||||||
/// fut: self.service.call(req),
|
/// fut: self.service.call(req),
|
||||||
/// sleep: Sleep::new(clock::now() + self.timeout),
|
/// sleep: Sleep::new(clock::now() + self.timeout),
|
||||||
@@ -55,26 +50,22 @@ where
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Timeout service in above example is decoupled from underlying service implementation and could
|
/// This wrapper service is decoupled from the underlying service implementation and could be
|
||||||
/// be applied to any service.
|
/// 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 wrapper. `Transform` is often
|
||||||
/// implemented for middleware, defining how to construct a middleware Service. A Service that is
|
/// implemented for middleware, defining how to construct a middleware Service. A Service that is
|
||||||
/// constructed by the factory takes the Service that follows it during execution as a parameter,
|
/// constructed by the factory takes the Service that follows it during execution as a parameter,
|
||||||
/// assuming ownership of the next Service.
|
/// assuming ownership of the next Service.
|
||||||
///
|
///
|
||||||
/// Factory for `Timeout` middleware from the above example could look like this:
|
/// A transform for the `Timeout` middleware could look like this:
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// pub struct TimeoutTransform {
|
/// pub struct TimeoutTransform {
|
||||||
/// timeout: Duration,
|
/// timeout: Duration,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl<S> Transform<S> for TimeoutTransform
|
/// impl<S: Service<Req>, Req> Transform<S, Req> for TimeoutTransform {
|
||||||
/// where
|
|
||||||
/// S: Service,
|
|
||||||
/// {
|
|
||||||
/// type Request = S::Request;
|
|
||||||
/// type Response = S::Response;
|
/// type Response = S::Response;
|
||||||
/// type Error = TimeoutError<S::Error>;
|
/// type Error = TimeoutError<S::Error>;
|
||||||
/// type InitError = S::Error;
|
/// type InitError = S::Error;
|
||||||
@@ -82,7 +73,7 @@ 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 {
|
/// ready(Ok(Timeout {
|
||||||
/// service,
|
/// service,
|
||||||
/// timeout: self.timeout,
|
/// timeout: self.timeout,
|
||||||
/// }))
|
/// }))
|
||||||
@@ -227,3 +218,53 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use core::time::Duration;
|
||||||
|
|
||||||
|
use actix_utils::future::{ready, Ready};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::Service;
|
||||||
|
|
||||||
|
// pseudo-doctest for Transform trait
|
||||||
|
pub struct TimeoutTransform {
|
||||||
|
timeout: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
// pseudo-doctest for Transform trait
|
||||||
|
impl<S: Service<Req>, Req> Transform<S, Req> for TimeoutTransform {
|
||||||
|
type Response = S::Response;
|
||||||
|
type Error = S::Error;
|
||||||
|
type InitError = S::Error;
|
||||||
|
type Transform = Timeout<S>;
|
||||||
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||||
|
|
||||||
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
|
ready(Ok(Timeout {
|
||||||
|
service,
|
||||||
|
_timeout: self.timeout,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pseudo-doctest for Transform trait
|
||||||
|
pub struct Timeout<S> {
|
||||||
|
service: S,
|
||||||
|
_timeout: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
// pseudo-doctest for Transform trait
|
||||||
|
impl<S: Service<Req>, Req> Service<Req> for Timeout<S> {
|
||||||
|
type Response = S::Response;
|
||||||
|
type Error = S::Error;
|
||||||
|
type Future = S::Future;
|
||||||
|
|
||||||
|
crate::forward_ready!(service);
|
||||||
|
|
||||||
|
fn call(&self, req: Req) -> Self::Future {
|
||||||
|
self.service.call(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -42,8 +42,8 @@ uri = ["http"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.4.0-beta.1"
|
actix-codec = "0.4.0-beta.1"
|
||||||
actix-rt = { version = "2.2.0", default-features = false }
|
actix-rt = { version = "2.2.0", default-features = false }
|
||||||
actix-service = "2.0.0-beta.5"
|
actix-service = "2.0.0"
|
||||||
actix-utils = "3.0.0-beta.2"
|
actix-utils = "3.0.0"
|
||||||
|
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
@@ -64,7 +64,7 @@ tokio-native-tls = { version = "0.3", optional = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2.0"
|
actix-rt = "2.2.0"
|
||||||
actix-server = "2.0.0-beta.3"
|
actix-server = "2.0.0-beta.5"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
futures-util = { version = "0.3.7", default-features = false, features = ["sink"] }
|
||||||
|
@@ -31,7 +31,7 @@ use std::{
|
|||||||
|
|
||||||
use actix_rt::net::TcpStream;
|
use actix_rt::net::TcpStream;
|
||||||
use actix_server::Server;
|
use actix_server::Server;
|
||||||
use actix_service::pipeline_factory;
|
use actix_service::ServiceFactoryExt as _;
|
||||||
use actix_tls::accept::rustls::{Acceptor as RustlsAcceptor, TlsStream};
|
use actix_tls::accept::rustls::{Acceptor as RustlsAcceptor, TlsStream};
|
||||||
use futures_util::future::ok;
|
use futures_util::future::ok;
|
||||||
use log::info;
|
use log::info;
|
||||||
@@ -39,14 +39,9 @@ use rustls::{
|
|||||||
internal::pemfile::certs, internal::pemfile::rsa_private_keys, NoClientAuth, ServerConfig,
|
internal::pemfile::certs, internal::pemfile::rsa_private_keys, NoClientAuth, ServerConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ServiceState {
|
|
||||||
num: Arc<AtomicUsize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
env::set_var("RUST_LOG", "actix=trace,basic=trace");
|
env::set_var("RUST_LOG", "info");
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let mut tls_config = ServerConfig::new(NoClientAuth::new());
|
let mut tls_config = ServerConfig::new(NoClientAuth::new());
|
||||||
@@ -73,7 +68,8 @@ async fn main() -> io::Result<()> {
|
|||||||
let count = Arc::clone(&count);
|
let count = Arc::clone(&count);
|
||||||
|
|
||||||
// Set up TLS service factory
|
// Set up TLS service factory
|
||||||
pipeline_factory(tls_acceptor.clone())
|
tls_acceptor
|
||||||
|
.clone()
|
||||||
.map_err(|err| println!("Rustls error: {:?}", err))
|
.map_err(|err| println!("Rustls error: {:?}", err))
|
||||||
.and_then(move |stream: TlsStream<TcpStream>| {
|
.and_then(move |stream: TlsStream<TcpStream>| {
|
||||||
let num = count.fetch_add(1, Ordering::Relaxed);
|
let num = count.fetch_add(1, Ordering::Relaxed);
|
||||||
|
@@ -56,7 +56,7 @@ pub enum Resolver {
|
|||||||
/// An interface for custom async DNS resolvers.
|
/// An interface for custom async DNS resolvers.
|
||||||
///
|
///
|
||||||
/// # Usage
|
/// # Usage
|
||||||
/// ```rust
|
/// ```
|
||||||
/// use std::net::SocketAddr;
|
/// use std::net::SocketAddr;
|
||||||
///
|
///
|
||||||
/// use actix_tls::connect::{Resolve, Resolver};
|
/// use actix_tls::connect::{Resolve, Resolver};
|
||||||
|
@@ -16,9 +16,9 @@ name = "actix_tracing"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-service = "2.0.0-beta.5"
|
actix-service = "2.0.0"
|
||||||
|
actix-utils = "3.0.0"
|
||||||
|
|
||||||
futures-util = { version = "0.3.7", default-features = false }
|
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-futures = "0.2"
|
tracing-futures = "0.2"
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@ use core::marker::PhantomData;
|
|||||||
use actix_service::{
|
use actix_service::{
|
||||||
apply, ApplyTransform, IntoServiceFactory, Service, ServiceFactory, Transform,
|
apply, ApplyTransform, IntoServiceFactory, Service, ServiceFactory, Transform,
|
||||||
};
|
};
|
||||||
use futures_util::future::{ok, Either, Ready};
|
use actix_utils::future::{ok, Either, Ready};
|
||||||
use tracing_futures::{Instrument, Instrumented};
|
use tracing_futures::{Instrument, Instrumented};
|
||||||
|
|
||||||
/// A `Service` implementation that automatically enters/exits tracing spans
|
/// A `Service` implementation that automatically enters/exits tracing spans
|
||||||
@@ -48,9 +48,9 @@ where
|
|||||||
.clone()
|
.clone()
|
||||||
.map(|span| tracing::span!(parent: &span, tracing::Level::INFO, "future"))
|
.map(|span| tracing::span!(parent: &span, tracing::Level::INFO, "future"))
|
||||||
{
|
{
|
||||||
Either::Right(fut.instrument(span))
|
Either::right(fut.instrument(span))
|
||||||
} else {
|
} else {
|
||||||
Either::Left(fut)
|
Either::left(fut)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,16 @@
|
|||||||
## Unreleased - 2021-xx-xx
|
## Unreleased - 2021-xx-xx
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0 - 2021-04-16
|
||||||
|
* No significant changes from `3.0.0-beta.4`.
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0-beta.4 - 2021-04-01
|
||||||
|
* Add `future::Either` type. [#305]
|
||||||
|
|
||||||
|
[#305]: https://github.com/actix/actix-net/pull/305
|
||||||
|
|
||||||
|
|
||||||
## 3.0.0-beta.3 - 2021-04-01
|
## 3.0.0-beta.3 - 2021-04-01
|
||||||
* Moved `mpsc` to own crate `local-channel`. [#301]
|
* Moved `mpsc` to own crate `local-channel`. [#301]
|
||||||
* Moved `task::LocalWaker` to own crate `local-waker`. [#301]
|
* Moved `task::LocalWaker` to own crate `local-waker`. [#301]
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-utils"
|
name = "actix-utils"
|
||||||
version = "3.0.0-beta.3"
|
version = "3.0.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
"Rob Ede <robjtede@icloud.com>",
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
]
|
]
|
||||||
description = "Utilities for the Actix ecosystem"
|
description = "Various utilities used in the Actix ecosystem"
|
||||||
keywords = ["network", "framework", "async", "futures"]
|
keywords = ["network", "framework", "async", "futures"]
|
||||||
repository = "https://github.com/actix/actix-net.git"
|
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
|
repository = "https://github.com/actix/actix-net"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ name = "actix_utils"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
pin-project-lite = "0.2"
|
||||||
local-waker = "0.1"
|
local-waker = "0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
91
actix-utils/src/future/either.rs
Normal file
91
actix-utils/src/future/either.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
//! A symmetric either future.
|
||||||
|
|
||||||
|
use core::{
|
||||||
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
/// Combines two different futures that have the same output type.
|
||||||
|
///
|
||||||
|
/// Construct variants with [`Either::left`] and [`Either::right`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// use actix_utils::future::{ready, Ready, Either};
|
||||||
|
///
|
||||||
|
/// # async fn run() {
|
||||||
|
/// let res = Either::<_, Ready<usize>>::left(ready(42));
|
||||||
|
/// assert_eq!(res.await, 42);
|
||||||
|
///
|
||||||
|
/// let res = Either::<Ready<usize>, _>::right(ready(43));
|
||||||
|
/// assert_eq!(res.await, 43);
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[project = EitherProj]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Either<L, R> {
|
||||||
|
/// A value of type `L`.
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
Left { #[pin] value: L },
|
||||||
|
|
||||||
|
/// A value of type `R`.
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
Right { #[pin] value: R },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> Either<L, R> {
|
||||||
|
/// Creates new `Either` using left variant.
|
||||||
|
pub fn left(value: L) -> Either<L, R> {
|
||||||
|
Either::Left { value }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates new `Either` using right variant.
|
||||||
|
pub fn right(value: R) -> Either<L, R> {
|
||||||
|
Either::Right { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Either<T, T> {
|
||||||
|
/// Unwraps into inner value when left and right have a common type.
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
match self {
|
||||||
|
Either::Left { value } => value,
|
||||||
|
Either::Right { value } => value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> Future for Either<L, R>
|
||||||
|
where
|
||||||
|
L: Future,
|
||||||
|
R: Future<Output = L::Output>,
|
||||||
|
{
|
||||||
|
type Output = L::Output;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
match self.project() {
|
||||||
|
EitherProj::Left { value } => value.poll(cx),
|
||||||
|
EitherProj::Right { value } => value.poll(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::future::{ready, Ready};
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_either() {
|
||||||
|
let res = Either::<_, Ready<usize>>::left(ready(42));
|
||||||
|
assert_eq!(res.await, 42);
|
||||||
|
|
||||||
|
let res = Either::<Ready<usize>, _>::right(ready(43));
|
||||||
|
assert_eq!(res.await, 43);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,9 @@
|
|||||||
//! Asynchronous values.
|
//! Asynchronous values.
|
||||||
|
|
||||||
|
mod either;
|
||||||
mod poll_fn;
|
mod poll_fn;
|
||||||
mod ready;
|
mod ready;
|
||||||
|
|
||||||
|
pub use self::either::Either;
|
||||||
pub use self::poll_fn::{poll_fn, PollFn};
|
pub use self::poll_fn::{poll_fn, PollFn};
|
||||||
pub use self::ready::{err, ok, ready, Ready};
|
pub use self::ready::{err, ok, ready, Ready};
|
||||||
|
@@ -7,7 +7,7 @@ use core::{
|
|||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Create a future driven by the provided function that receives a task context.
|
/// Creates a future driven by the provided function that receives a task context.
|
||||||
pub fn poll_fn<F, T>(f: F) -> PollFn<F>
|
pub fn poll_fn<F, T>(f: F) -> PollFn<F>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut Context<'_>) -> Poll<T>,
|
F: FnMut(&mut Context<'_>) -> Poll<T>,
|
||||||
|
@@ -69,7 +69,7 @@ pub fn ready<T>(val: T) -> Ready<T> {
|
|||||||
Ready { val: Some(val) }
|
Ready { val: Some(val) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a future that is immediately ready with a success value.
|
/// Creates a future that is immediately ready with a success value.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
@@ -84,7 +84,7 @@ pub fn ok<T, E>(val: T) -> Ready<Result<T, E>> {
|
|||||||
Ready { val: Some(Ok(val)) }
|
Ready { val: Some(Ok(val)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a future that is immediately ready with an error value.
|
/// Creates a future that is immediately ready with an error value.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
//! Various utilities for the Actix ecosystem.
|
//! Various utilities used in the Actix ecosystem.
|
||||||
|
|
||||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
Reference in New Issue
Block a user