mirror of
https://github.com/OMGeeky/tarpc.git
synced 2026-02-23 15:49:54 +01:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bed85e2827 | ||
|
|
93f3880025 | ||
|
|
878f594d5b | ||
|
|
aa9bbad109 | ||
|
|
7e872ce925 | ||
|
|
62541b709d | ||
|
|
8c43f94fb6 | ||
|
|
7fa4e5064d | ||
|
|
94db7610bb | ||
|
|
0c08d5e8ca | ||
|
|
75b15fe2aa | ||
|
|
863a08d87e | ||
|
|
49ba8f8b1b | ||
|
|
d832209da3 | ||
|
|
584426d414 | ||
|
|
50eb80c883 | ||
|
|
1f0c80d8c9 | ||
|
|
99bf3e62a3 | ||
|
|
68863e3db0 | ||
|
|
453ba1c074 | ||
|
|
e3eac1b4f5 | ||
|
|
0e102288a5 | ||
|
|
4c8ba41b2f | ||
|
|
946c627579 | ||
|
|
104dd71bba | ||
|
|
012c481861 | ||
|
|
dc12bd09aa | ||
|
|
2594ea8ce9 | ||
|
|
839b87e394 | ||
|
|
57d0638a99 | ||
|
|
a3a6404a30 | ||
|
|
b36eac80b1 | ||
|
|
d7070e4bc3 | ||
|
|
b5d1828308 | ||
|
|
92cfe63c4f | ||
|
|
839a2f067c | ||
|
|
b5d593488c | ||
|
|
eea38b8bf4 | ||
|
|
70493c15f4 | ||
|
|
f7c5d6a7c3 | ||
|
|
98c5d2a18b | ||
|
|
46b534f7c6 | ||
|
|
42b4fc52b1 | ||
|
|
350dbcdad0 | ||
|
|
b1b4461d89 | ||
|
|
f694b7573a | ||
|
|
1e680e3a5a | ||
|
|
2591d21e94 | ||
|
|
6632f68d95 | ||
|
|
25985ad56a | ||
|
|
d6a24e9420 | ||
|
|
281a78f3c7 | ||
|
|
a0787d0091 | ||
|
|
d2acba0e8a | ||
|
|
ea7b6763c4 | ||
|
|
eb67c540b9 | ||
|
|
4151d0abd3 | ||
|
|
d0c11a6efa | ||
|
|
82c4da1743 | ||
|
|
0a15e0b75c | ||
|
|
0b315c29bf | ||
|
|
56f09bf61f | ||
|
|
6d82e82419 | ||
|
|
9bebaf814a |
92
.github/workflows/main.yml
vendored
92
.github/workflows/main.yml
vendored
@@ -14,99 +14,57 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel previous
|
||||
uses: styfle/cancel-workflow-action@0.7.0
|
||||
uses: styfle/cancel-workflow-action@0.10.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
target: mipsel-unknown-linux-gnu
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all-features
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all-features --target mipsel-unknown-linux-gnu
|
||||
targets: mipsel-unknown-linux-gnu
|
||||
- run: cargo check --all-features
|
||||
- run: cargo check --all-features --target mipsel-unknown-linux-gnu
|
||||
|
||||
test:
|
||||
name: Test Suite
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel previous
|
||||
uses: styfle/cancel-workflow-action@0.7.0
|
||||
uses: styfle/cancel-workflow-action@0.10.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path tarpc/Cargo.toml --features serde1
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path tarpc/Cargo.toml --features tokio1
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path tarpc/Cargo.toml --features serde-transport
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path tarpc/Cargo.toml --features tcp
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all-features
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo test
|
||||
- run: cargo test --manifest-path tarpc/Cargo.toml --features serde1
|
||||
- run: cargo test --manifest-path tarpc/Cargo.toml --features tokio1
|
||||
- run: cargo test --manifest-path tarpc/Cargo.toml --features serde-transport
|
||||
- run: cargo test --manifest-path tarpc/Cargo.toml --features tcp
|
||||
- run: cargo test --all-features
|
||||
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel previous
|
||||
uses: styfle/cancel-workflow-action@0.7.0
|
||||
uses: styfle/cancel-workflow-action@0.10.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
components: rustfmt
|
||||
- run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cancel previous
|
||||
uses: styfle/cancel-workflow-action@0.7.0
|
||||
uses: styfle/cancel-workflow-action@0.10.0
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add clippy
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --all-features -- -D warnings
|
||||
components: clippy
|
||||
- run: cargo clippy --all-features -- -D warnings
|
||||
|
||||
20
README.md
20
README.md
@@ -40,7 +40,7 @@ rather than in a separate language such as .proto. This means there's no separat
|
||||
process, and no context switching between different languages.
|
||||
|
||||
Some other features of tarpc:
|
||||
- Pluggable transport: any type impling `Stream<Item = Request> + Sink<Response>` can be
|
||||
- Pluggable transport: any type implementing `Stream<Item = Request> + Sink<Response>` can be
|
||||
used as a transport to connect the client and server.
|
||||
- `Send + 'static` optional: if the transport doesn't require it, neither does tarpc!
|
||||
- Cascading cancellation: dropping a request will send a cancellation message to the server.
|
||||
@@ -55,7 +55,7 @@ Some other features of tarpc:
|
||||
[tracing](https://github.com/tokio-rs/tracing) primitives extended with
|
||||
[OpenTelemetry](https://opentelemetry.io/) traces. Using a compatible tracing subscriber like
|
||||
[Jaeger](https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-jaeger),
|
||||
each RPC can be traced through the client, server, amd other dependencies downstream of the
|
||||
each RPC can be traced through the client, server, and other dependencies downstream of the
|
||||
server. Even for applications not connected to a distributed tracing collector, the
|
||||
instrumentation can also be ingested by regular loggers like
|
||||
[env_logger](https://github.com/env-logger-rs/env_logger/).
|
||||
@@ -67,7 +67,7 @@ Some other features of tarpc:
|
||||
Add to your `Cargo.toml` dependencies:
|
||||
|
||||
```toml
|
||||
tarpc = "0.26"
|
||||
tarpc = "0.32"
|
||||
```
|
||||
|
||||
The `tarpc::service` attribute expands to a collection of items that form an rpc service.
|
||||
@@ -80,8 +80,9 @@ This example uses [tokio](https://tokio.rs), so add the following dependencies t
|
||||
your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
futures = "1.0"
|
||||
tarpc = { version = "0.26", features = ["tokio1"] }
|
||||
anyhow = "1.0"
|
||||
futures = "0.3"
|
||||
tarpc = { version = "0.31", features = ["tokio1"] }
|
||||
tokio = { version = "1.0", features = ["macros"] }
|
||||
```
|
||||
|
||||
@@ -99,9 +100,8 @@ use futures::{
|
||||
};
|
||||
use tarpc::{
|
||||
client, context,
|
||||
server::{self, Incoming},
|
||||
server::{self, incoming::Incoming, Channel},
|
||||
};
|
||||
use std::io;
|
||||
|
||||
// This is the service definition. It looks a lot like a trait definition.
|
||||
// It defines one RPC, hello, which takes one arg, name, and returns a String.
|
||||
@@ -128,7 +128,7 @@ impl World for HelloServer {
|
||||
type HelloFut = Ready<String>;
|
||||
|
||||
fn hello(self, _: context::Context, name: String) -> Self::HelloFut {
|
||||
future::ready(format!("Hello, {}!", name))
|
||||
future::ready(format!("Hello, {name}!"))
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -140,7 +140,7 @@ available behind the `tcp` feature.
|
||||
|
||||
```rust
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let (client_transport, server_transport) = tarpc::transport::channel::unbounded();
|
||||
|
||||
let server = server::BaseChannel::with_defaults(server_transport);
|
||||
@@ -155,7 +155,7 @@ async fn main() -> io::Result<()> {
|
||||
// specifies a deadline and trace information which can be helpful in debugging requests.
|
||||
let hello = client.hello(context::current(), "Stim".to_string()).await?;
|
||||
|
||||
println!("{}", hello);
|
||||
println!("{hello}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
107
RELEASES.md
107
RELEASES.md
@@ -1,3 +1,102 @@
|
||||
## 0.32.0 (2023-03-24)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- As part of a fix to return more channel errors in RPC results, a few error types have changed:
|
||||
|
||||
0. `client::RpcError::Disconnected` was split into the following errors:
|
||||
- Shutdown: the client was shutdown, either intentionally or due to an error. If due to an
|
||||
error, pending RPCs should see the more specific errors below.
|
||||
- Send: an RPC message failed to send over the transport. Only the RPC that failed to be sent
|
||||
will see this error.
|
||||
- Receive: a fatal error occurred while receiving from the transport. All in-flight RPCs will
|
||||
receive this error.
|
||||
0. `client::ChannelError` and `server::ChannelError` are unified in `tarpc::ChannelError`.
|
||||
Previously, server transport errors would not indicate during which activity the transport
|
||||
error occurred. Now, just like the client already was, it will be specific: reading, readying,
|
||||
sending, flushing, or closing.
|
||||
|
||||
## 0.31.0 (2022-11-03)
|
||||
|
||||
### New Features
|
||||
|
||||
This release adds Unix Domain Sockets to the `serde_transport` module.
|
||||
To use it, enable the "unix" feature. See the docs for more information.
|
||||
|
||||
## 0.30.0 (2022-08-12)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Some types that impl Future are now annotated with `#[must_use]`. Code that previously created
|
||||
these types but did not use them will now receive a warning. Code that disallows warnings will
|
||||
receive a compilation error.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Servers will more reliably clean up request state for requests with long deadlines when response
|
||||
processing is aborted without sending a response.
|
||||
|
||||
### Other Changes
|
||||
|
||||
- `TrackedRequest` now contains a response guard that can be used to ensure state cleanup for
|
||||
aborted requests. (This was already handled automatically by `InFlightRequests`).
|
||||
- When the feature serde-transport is enabled, the crate tokio_serde is now re-exported.
|
||||
|
||||
## 0.29.0 (2022-05-26)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
`Context.deadline` is now serialized as a Duration. This prevents clock skew from affecting deadline
|
||||
behavior. For more details see https://github.com/google/tarpc/pull/367 and its [related
|
||||
issue](https://github.com/google/tarpc/issues/366).
|
||||
|
||||
## 0.28.0 (2022-04-06)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- The minimum supported Rust version has increased to 1.58.0.
|
||||
- The version of opentelemetry depended on by tarpc has increased to 0.17.0.
|
||||
|
||||
## 0.27.2 (2021-10-08)
|
||||
|
||||
### Fixes
|
||||
|
||||
Clients will now close their transport before dropping it. An attempt at a clean shutdown can help
|
||||
the server drop its connections more quickly.
|
||||
|
||||
## 0.27.1 (2021-09-22)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
#### RPC error type is changing
|
||||
|
||||
RPC return types are changing from `Result<Response, io::Error>` to `Result<Response,
|
||||
tarpc::client::RpcError>`.
|
||||
|
||||
Becaue tarpc is a library, not an application, it should strive to
|
||||
use structured errors in its API so that users have maximal flexibility
|
||||
in how they handle errors. io::Error makes that hard, because it is a
|
||||
kitchen-sink error type.
|
||||
|
||||
RPCs in particular only have 3 classes of errors:
|
||||
|
||||
- The connection breaks.
|
||||
- The request expires.
|
||||
- The server decides not to process the request.
|
||||
|
||||
RPC responses can also contain application-specific errors, but from the
|
||||
perspective of the RPC library, those are opaque to the framework, classified
|
||||
as successful responsees.
|
||||
|
||||
### Open Telemetry
|
||||
|
||||
The Opentelemetry dependency is updated to version 0.16.x.
|
||||
|
||||
## 0.27.0 (2021-09-22)
|
||||
|
||||
This version was yanked due to tarpc-plugins version mismatches.
|
||||
|
||||
|
||||
## 0.26.0 (2021-04-14)
|
||||
|
||||
### New Features
|
||||
@@ -68,10 +167,10 @@ tracing_subscriber::fmt.
|
||||
|
||||
### References
|
||||
|
||||
[1] https://github.com/tokio-rs/tracing
|
||||
[2] https://opentelemetry.io
|
||||
[3] https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-jaeger
|
||||
[4] https://github.com/env-logger-rs/env_logger
|
||||
1. https://github.com/tokio-rs/tracing
|
||||
2. https://opentelemetry.io
|
||||
3. https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-jaeger
|
||||
4. https://github.com/env-logger-rs/env_logger
|
||||
|
||||
## 0.25.0 (2021-03-10)
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[package]
|
||||
name = "tarpc-example-service"
|
||||
version = "0.10.0"
|
||||
version = "0.14.0"
|
||||
rust-version = "1.56"
|
||||
authors = ["Tim Kuehn <tikue@google.com>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
documentation = "https://docs.rs/tarpc-example-service"
|
||||
homepage = "https://github.com/google/tarpc"
|
||||
@@ -14,19 +15,17 @@ description = "An example server built on tarpc."
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
clap = "3.0.0-beta.2"
|
||||
clap = { version = "3.0.0-rc.9", features = ["derive"] }
|
||||
log = "0.4"
|
||||
futures = "0.3"
|
||||
opentelemetry = { version = "0.13", features = ["rt-tokio"] }
|
||||
opentelemetry-jaeger = { version = "0.12", features = ["tokio"] }
|
||||
opentelemetry = { version = "0.17", features = ["rt-tokio"] }
|
||||
opentelemetry-jaeger = { version = "0.16", features = ["rt-tokio"] }
|
||||
rand = "0.8"
|
||||
serde = { version = "1.0" }
|
||||
tarpc = { version = "0.26", path = "../tarpc", features = ["full"] }
|
||||
tarpc = { version = "0.32", path = "../tarpc", features = ["full"] }
|
||||
tokio = { version = "1", features = ["macros", "net", "rt-multi-thread"] }
|
||||
tokio-serde = { version = "0.8", features = ["json"] }
|
||||
tracing = { version = "0.1" }
|
||||
tracing-opentelemetry = "0.12"
|
||||
tracing-subscriber = "0.2"
|
||||
tracing-opentelemetry = "0.17"
|
||||
tracing-subscriber = {version = "0.3", features = ["env-filter"]}
|
||||
|
||||
[lib]
|
||||
name = "service"
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use clap::Clap;
|
||||
use clap::Parser;
|
||||
use service::{init_tracing, WorldClient};
|
||||
use std::{net::SocketAddr, time::Duration};
|
||||
use tarpc::{client, context, tokio_serde::formats::Json};
|
||||
use tokio::time::sleep;
|
||||
use tracing::Instrument;
|
||||
|
||||
#[derive(Clap)]
|
||||
#[derive(Parser)]
|
||||
struct Flags {
|
||||
/// Sets the server address to connect to.
|
||||
#[clap(long)]
|
||||
@@ -26,7 +26,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
let flags = Flags::parse();
|
||||
init_tracing("Tarpc Example Client")?;
|
||||
|
||||
let transport = tarpc::serde_transport::tcp::connect(flags.server_addr, Json::default);
|
||||
let mut transport = tarpc::serde_transport::tcp::connect(flags.server_addr, Json::default);
|
||||
transport.config_mut().max_frame_length(usize::MAX);
|
||||
|
||||
// WorldClient is generated by the service attribute. It has a constructor `new` that takes a
|
||||
// config and any Transport as input.
|
||||
@@ -42,7 +43,10 @@ async fn main() -> anyhow::Result<()> {
|
||||
.instrument(tracing::info_span!("Two Hellos"))
|
||||
.await;
|
||||
|
||||
tracing::info!("{:?}", hello);
|
||||
match hello {
|
||||
Ok(hello) => tracing::info!("{hello:?}"),
|
||||
Err(e) => tracing::warn!("{:?}", anyhow::Error::from(e)),
|
||||
}
|
||||
|
||||
// Let the background span processor finish.
|
||||
sleep(Duration::from_micros(1)).await;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use clap::Clap;
|
||||
use clap::Parser;
|
||||
use futures::{future, prelude::*};
|
||||
use rand::{
|
||||
distributions::{Distribution, Uniform},
|
||||
@@ -17,12 +17,12 @@ use std::{
|
||||
};
|
||||
use tarpc::{
|
||||
context,
|
||||
server::{self, Channel, Incoming},
|
||||
server::{self, incoming::Incoming, Channel},
|
||||
tokio_serde::formats::Json,
|
||||
};
|
||||
use tokio::time;
|
||||
|
||||
#[derive(Clap)]
|
||||
#[derive(Parser)]
|
||||
struct Flags {
|
||||
/// Sets the port number to listen on.
|
||||
#[clap(long)]
|
||||
@@ -40,7 +40,7 @@ impl World for HelloServer {
|
||||
let sleep_time =
|
||||
Duration::from_millis(Uniform::new_inclusive(1, 10).sample(&mut thread_rng()));
|
||||
time::sleep(sleep_time).await;
|
||||
format!("Hello, {}! You are connected from {}", name, self.0)
|
||||
format!("Hello, {name}! You are connected from {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
// JSON transport is provided by the json_transport tarpc module. It makes it easy
|
||||
// to start up a serde-powered json serialization strategy over TCP.
|
||||
let mut listener = tarpc::serde_transport::tcp::listen(&server_addr, Json::default).await?;
|
||||
tracing::info!("Listening on port {}", listener.local_addr().port());
|
||||
listener.config_mut().max_frame_length(usize::MAX);
|
||||
listener
|
||||
// Ignore accept errors.
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[package]
|
||||
name = "tarpc-plugins"
|
||||
version = "0.11.0"
|
||||
version = "0.12.0"
|
||||
rust-version = "1.56"
|
||||
authors = ["Adam Wright <adam.austin.wright@gmail.com>", "Tim Kuehn <timothy.j.kuehn@gmail.com>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
documentation = "https://docs.rs/tarpc-plugins"
|
||||
homepage = "https://github.com/google/tarpc"
|
||||
|
||||
9
plugins/LICENSE
Normal file
9
plugins/LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2016 Google Inc. All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -83,7 +83,7 @@ impl Parse for Service {
|
||||
ident_errors,
|
||||
syn::Error::new(
|
||||
rpc.ident.span(),
|
||||
format!("method name conflicts with generated fn `{}::serve`", ident)
|
||||
format!("method name conflicts with generated fn `{ident}::serve`")
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -270,14 +270,14 @@ pub fn service(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let methods = rpcs.iter().map(|rpc| &rpc.ident).collect::<Vec<_>>();
|
||||
let request_names = methods
|
||||
.iter()
|
||||
.map(|m| format!("{}.{}", ident, m))
|
||||
.map(|m| format!("{ident}.{m}"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
ServiceGenerator {
|
||||
response_fut_name,
|
||||
service_ident: ident,
|
||||
server_ident: &format_ident!("Serve{}", ident),
|
||||
response_fut_ident: &Ident::new(&response_fut_name, ident.span()),
|
||||
response_fut_ident: &Ident::new(response_fut_name, ident.span()),
|
||||
client_ident: &format_ident!("{}Client", ident),
|
||||
request_ident: &format_ident!("{}Request", ident),
|
||||
response_ident: &format_ident!("{}Response", ident),
|
||||
@@ -285,7 +285,7 @@ pub fn service(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
args,
|
||||
method_attrs: &rpcs.iter().map(|rpc| &*rpc.attrs).collect::<Vec<_>>(),
|
||||
method_idents: &methods,
|
||||
request_names: &*request_names,
|
||||
request_names: &request_names,
|
||||
attrs,
|
||||
rpcs,
|
||||
return_types: &rpcs
|
||||
@@ -306,7 +306,7 @@ pub fn service(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
.collect::<Vec<_>>(),
|
||||
future_types: &camel_case_fn_names
|
||||
.iter()
|
||||
.map(|name| parse_str(&format!("{}Fut", name)).unwrap())
|
||||
.map(|name| parse_str(&format!("{name}Fut")).unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
derive_serialize: derive_serialize.as_ref(),
|
||||
}
|
||||
@@ -406,14 +406,10 @@ fn verify_types_were_provided(
|
||||
) -> syn::Result<()> {
|
||||
let mut result = Ok(());
|
||||
for (method, expected) in expected {
|
||||
if provided
|
||||
.iter()
|
||||
.find(|typedecl| typedecl.ident == expected)
|
||||
.is_none()
|
||||
{
|
||||
if !provided.iter().any(|typedecl| typedecl.ident == expected) {
|
||||
let mut e = syn::Error::new(
|
||||
span,
|
||||
format!("not all trait items implemented, missing: `{}`", expected),
|
||||
format!("not all trait items implemented, missing: `{expected}`"),
|
||||
);
|
||||
let fn_span = method.sig.fn_token.span();
|
||||
e.extend(syn::Error::new(
|
||||
@@ -483,7 +479,7 @@ impl<'a> ServiceGenerator<'a> {
|
||||
),
|
||||
output,
|
||||
)| {
|
||||
let ty_doc = format!("The response future returned by {}.", ident);
|
||||
let ty_doc = format!("The response future returned by [`{service_ident}::{ident}`].");
|
||||
quote! {
|
||||
#[doc = #ty_doc]
|
||||
type #future_type: std::future::Future<Output = #output>;
|
||||
@@ -582,6 +578,7 @@ impl<'a> ServiceGenerator<'a> {
|
||||
|
||||
quote! {
|
||||
/// The request sent over the wire from the client to the server.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug)]
|
||||
#derive_serialize
|
||||
#vis enum #request_ident {
|
||||
@@ -602,6 +599,7 @@ impl<'a> ServiceGenerator<'a> {
|
||||
|
||||
quote! {
|
||||
/// The response sent over the wire from the server to the client.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug)]
|
||||
#derive_serialize
|
||||
#vis enum #response_ident {
|
||||
@@ -622,6 +620,7 @@ impl<'a> ServiceGenerator<'a> {
|
||||
|
||||
quote! {
|
||||
/// A future resolving to a server response.
|
||||
#[allow(missing_docs)]
|
||||
#vis enum #response_fut_ident<S: #service_ident> {
|
||||
#( #camel_case_idents(<S as #service_ident>::#future_types) ),*
|
||||
}
|
||||
@@ -747,7 +746,7 @@ impl<'a> ServiceGenerator<'a> {
|
||||
#[allow(unused)]
|
||||
#( #method_attrs )*
|
||||
#vis fn #method_idents(&self, ctx: tarpc::context::Context, #( #args ),*)
|
||||
-> impl std::future::Future<Output = std::io::Result<#return_types>> + '_ {
|
||||
-> impl std::future::Future<Output = Result<#return_types, tarpc::client::RpcError>> + '_ {
|
||||
let request = #request_ident::#camel_case_idents { #( #arg_pats ),* };
|
||||
let resp = self.0.call(ctx, #request_names, request);
|
||||
async move {
|
||||
|
||||
@@ -1,26 +1,41 @@
|
||||
[package]
|
||||
name = "tarpc"
|
||||
version = "0.26.0"
|
||||
authors = ["Adam Wright <adam.austin.wright@gmail.com>", "Tim Kuehn <timothy.j.kuehn@gmail.com>"]
|
||||
edition = "2018"
|
||||
version = "0.32.0"
|
||||
rust-version = "1.58.0"
|
||||
authors = [
|
||||
"Adam Wright <adam.austin.wright@gmail.com>",
|
||||
"Tim Kuehn <timothy.j.kuehn@gmail.com>",
|
||||
]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
documentation = "https://docs.rs/tarpc"
|
||||
homepage = "https://github.com/google/tarpc"
|
||||
repository = "https://github.com/google/tarpc"
|
||||
keywords = ["rpc", "network", "server", "api", "microservices"]
|
||||
categories = ["asynchronous", "network-programming"]
|
||||
readme = "../README.md"
|
||||
readme = "README.md"
|
||||
description = "An RPC framework for Rust with a focus on ease of use."
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
serde1 = ["tarpc-plugins/serde1", "serde", "serde/derive"]
|
||||
tokio1 = ["tokio/rt-multi-thread"]
|
||||
tokio1 = ["tokio/rt"]
|
||||
serde-transport = ["serde1", "tokio1", "tokio-serde", "tokio-util/codec"]
|
||||
serde-transport-json = ["tokio-serde/json"]
|
||||
serde-transport-bincode = ["tokio-serde/bincode"]
|
||||
tcp = ["tokio/net"]
|
||||
unix = ["tokio/net"]
|
||||
|
||||
full = ["serde1", "tokio1", "serde-transport", "tcp"]
|
||||
full = [
|
||||
"serde1",
|
||||
"tokio1",
|
||||
"serde-transport",
|
||||
"serde-transport-json",
|
||||
"serde-transport-bincode",
|
||||
"tcp",
|
||||
"unix",
|
||||
]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "google/tarpc" }
|
||||
@@ -34,14 +49,17 @@ pin-project = "1.0"
|
||||
rand = "0.8"
|
||||
serde = { optional = true, version = "1.0", features = ["derive"] }
|
||||
static_assertions = "1.1.0"
|
||||
tarpc-plugins = { path = "../plugins", version = "0.11" }
|
||||
tarpc-plugins = { path = "../plugins", version = "0.12" }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1", features = ["time"] }
|
||||
tokio-util = { version = "0.6.3", features = ["time"] }
|
||||
tokio-util = { version = "0.7.3", features = ["time"] }
|
||||
tokio-serde = { optional = true, version = "0.8" }
|
||||
tracing = { version = "0.1", default-features = false, features = ["attributes", "log"] }
|
||||
tracing-opentelemetry = { version = "0.12", default-features = false }
|
||||
opentelemetry = { version = "0.13", default-features = false }
|
||||
tracing = { version = "0.1", default-features = false, features = [
|
||||
"attributes",
|
||||
"log",
|
||||
] }
|
||||
tracing-opentelemetry = { version = "0.17.2", default-features = false }
|
||||
opentelemetry = { version = "0.17.0", default-features = false }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -50,14 +68,18 @@ bincode = "1.3"
|
||||
bytes = { version = "1", features = ["serde"] }
|
||||
flate2 = "1.0"
|
||||
futures-test = "0.3"
|
||||
opentelemetry = { version = "0.13", default-features = false, features = ["rt-tokio"] }
|
||||
opentelemetry-jaeger = { version = "0.12", features = ["tokio"] }
|
||||
opentelemetry = { version = "0.17.0", default-features = false, features = [
|
||||
"rt-tokio",
|
||||
] }
|
||||
opentelemetry-jaeger = { version = "0.16.0", features = ["rt-tokio"] }
|
||||
pin-utils = "0.1.0-alpha"
|
||||
serde_bytes = "0.11"
|
||||
tracing-subscriber = "0.2"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
tokio = { version = "1", features = ["full", "test-util"] }
|
||||
tokio-serde = { version = "0.8", features = ["json", "bincode"] }
|
||||
trybuild = "1.0"
|
||||
tokio-rustls = "0.23"
|
||||
rustls-pemfile = "1.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -83,6 +105,10 @@ required-features = ["full"]
|
||||
name = "custom_transport"
|
||||
required-features = ["serde1", "tokio1", "serde-transport"]
|
||||
|
||||
[[example]]
|
||||
name = "tls_over_tcp"
|
||||
required-features = ["full"]
|
||||
|
||||
[[test]]
|
||||
name = "service_functional"
|
||||
required-features = ["serde-transport"]
|
||||
|
||||
9
tarpc/LICENSE
Normal file
9
tarpc/LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2016 Google Inc. All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
11
tarpc/examples/certs/eddsa/client.cert
Normal file
11
tarpc/examples/certs/eddsa/client.cert
Normal file
@@ -0,0 +1,11 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBlDCCAUagAwIBAgICAxUwBQYDK2VwMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVk
|
||||
RFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTIzMDMxNzEwMTEwNFoXDTI4MDkw
|
||||
NjEwMTEwNFowGjEYMBYGA1UEAwwPcG9ueXRvd24gY2xpZW50MCowBQYDK2VwAyEA
|
||||
NTKuLume19IhJfEFd/5OZUuYDKZH6xvy4AGver17OoejgZswgZgwDAYDVR0TAQH/
|
||||
BAIwADALBgNVHQ8EBAMCBsAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwHQYDVR0O
|
||||
BBYEFDjdrlMu4tyw5MHtbg7WnzSGRBpFMEQGA1UdIwQ9MDuAFHIl7fHKWP6/l8FE
|
||||
fI2YEIM3oHxKoSCkHjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQYIBezAF
|
||||
BgMrZXADQQCaahfj/QLxoCOpvl6y0ZQ9CpojPqBnxV3460j5nUOp040Va2MpF137
|
||||
izCBY7LwgUE/YG6E+kH30G4jMEnqVEYK
|
||||
-----END CERTIFICATE-----
|
||||
19
tarpc/examples/certs/eddsa/client.chain
Normal file
19
tarpc/examples/certs/eddsa/client.chain
Normal file
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBeDCCASqgAwIBAgIBezAFBgMrZXAwHDEaMBgGA1UEAwwRcG9ueXRvd24gRWRE
|
||||
U0EgQ0EwHhcNMjMwMzE3MTAxMTA0WhcNMzMwMzE0MTAxMTA0WjAuMSwwKgYDVQQD
|
||||
DCNwb255dG93biBFZERTQSBsZXZlbCAyIGludGVybWVkaWF0ZTAqMAUGAytlcAMh
|
||||
AEFsAexz4x2R4k4+PnTbvRVn0r3F/qw/zVnNBxfGcoEpo38wfTAdBgNVHQ4EFgQU
|
||||
ciXt8cpY/r+XwUR8jZgQgzegfEowIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsG
|
||||
AQUFBwMCMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgH+MB8GA1UdIwQYMBaAFKYU
|
||||
oLdKeY7mp7QgMZKrkVtSWYBKMAUGAytlcANBAHVpNpCV8nu4fkH3Smikx5A9qtHc
|
||||
zgLIyp+wrF1a4YSa6sfTvuQmJd5aF23OXgq5grCOPXtdpHO50Mx5Qy74zQg=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBTDCB/6ADAgECAhRZLuF0TWjDs/31OO8VeKHkNIJQaDAFBgMrZXAwHDEaMBgG
|
||||
A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMjMwMzE3MTAxMTA0WhcNMzMwMzE0
|
||||
MTAxMTA0WjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh
|
||||
ABRPZ4TiuBE8CqAFByZvqpMo/unjnnryfG2AkkWGXpa3o1MwUTAdBgNVHQ4EFgQU
|
||||
phSgt0p5juantCAxkquRW1JZgEowHwYDVR0jBBgwFoAUphSgt0p5juantCAxkquR
|
||||
W1JZgEowDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQB29o8erJA0/a8/xOHilOCC
|
||||
t/s5wPHHnS5NSKx/m2N2nRn3zPxEnETlrAmGulJoeKOx8OblwmPi9rBT2K+QY2UB
|
||||
-----END CERTIFICATE-----
|
||||
3
tarpc/examples/certs/eddsa/client.key
Normal file
3
tarpc/examples/certs/eddsa/client.key
Normal file
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIIJX9ThTHpVS1SNZb6HP4myg4fRInIVGunTRdgnc+weH
|
||||
-----END PRIVATE KEY-----
|
||||
12
tarpc/examples/certs/eddsa/end.cert
Normal file
12
tarpc/examples/certs/eddsa/end.cert
Normal file
@@ -0,0 +1,12 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAWqgAwIBAgICAcgwBQYDK2VwMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVk
|
||||
RFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTIzMDMxNzEwMTEwNFoXDTI4MDkw
|
||||
NjEwMTEwNFowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wKjAFBgMrZXADIQDc
|
||||
RLl3/N2tPoWnzBV3noVn/oheEl8IUtiY11Vg/QXTUKOBwDCBvTAMBgNVHRMBAf8E
|
||||
AjAAMAsGA1UdDwQEAwIGwDAdBgNVHQ4EFgQUk7U2mnxedNWBAH84BsNy5si3ZQow
|
||||
RAYDVR0jBD0wO4AUciXt8cpY/r+XwUR8jZgQgzegfEqhIKQeMBwxGjAYBgNVBAMM
|
||||
EXBvbnl0b3duIEVkRFNBIENBggF7MDsGA1UdEQQ0MDKCDnRlc3RzZXJ2ZXIuY29t
|
||||
ghVzZWNvbmQudGVzdHNlcnZlci5jb22CCWxvY2FsaG9zdDAFBgMrZXADQQCFWIcF
|
||||
9FiztCuUNzgXDNu5kshuflt0RjkjWpGlWzQjGoYM2IvYhNVPeqnCiY92gqwDSBtq
|
||||
amD2TBup4eNUCsQB
|
||||
-----END CERTIFICATE-----
|
||||
19
tarpc/examples/certs/eddsa/end.chain
Normal file
19
tarpc/examples/certs/eddsa/end.chain
Normal file
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBeDCCASqgAwIBAgIBezAFBgMrZXAwHDEaMBgGA1UEAwwRcG9ueXRvd24gRWRE
|
||||
U0EgQ0EwHhcNMjMwMzE3MTAxMTA0WhcNMzMwMzE0MTAxMTA0WjAuMSwwKgYDVQQD
|
||||
DCNwb255dG93biBFZERTQSBsZXZlbCAyIGludGVybWVkaWF0ZTAqMAUGAytlcAMh
|
||||
AEFsAexz4x2R4k4+PnTbvRVn0r3F/qw/zVnNBxfGcoEpo38wfTAdBgNVHQ4EFgQU
|
||||
ciXt8cpY/r+XwUR8jZgQgzegfEowIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsG
|
||||
AQUFBwMCMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgH+MB8GA1UdIwQYMBaAFKYU
|
||||
oLdKeY7mp7QgMZKrkVtSWYBKMAUGAytlcANBAHVpNpCV8nu4fkH3Smikx5A9qtHc
|
||||
zgLIyp+wrF1a4YSa6sfTvuQmJd5aF23OXgq5grCOPXtdpHO50Mx5Qy74zQg=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBTDCB/6ADAgECAhRZLuF0TWjDs/31OO8VeKHkNIJQaDAFBgMrZXAwHDEaMBgG
|
||||
A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMjMwMzE3MTAxMTA0WhcNMzMwMzE0
|
||||
MTAxMTA0WjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh
|
||||
ABRPZ4TiuBE8CqAFByZvqpMo/unjnnryfG2AkkWGXpa3o1MwUTAdBgNVHQ4EFgQU
|
||||
phSgt0p5juantCAxkquRW1JZgEowHwYDVR0jBBgwFoAUphSgt0p5juantCAxkquR
|
||||
W1JZgEowDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQB29o8erJA0/a8/xOHilOCC
|
||||
t/s5wPHHnS5NSKx/m2N2nRn3zPxEnETlrAmGulJoeKOx8OblwmPi9rBT2K+QY2UB
|
||||
-----END CERTIFICATE-----
|
||||
3
tarpc/examples/certs/eddsa/end.key
Normal file
3
tarpc/examples/certs/eddsa/end.key
Normal file
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIMU6xGVe8JTpZ3bN/wajHfw6pEHt0Rd7wPBxds9eEFy2
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -7,8 +7,8 @@ use tarpc::{
|
||||
client, context,
|
||||
serde_transport::tcp,
|
||||
server::{BaseChannel, Channel},
|
||||
tokio_serde::formats::Bincode,
|
||||
};
|
||||
use tokio_serde::formats::Bincode;
|
||||
|
||||
/// Type of compression that should be enabled on the request. The transport is free to ignore this.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
|
||||
@@ -54,7 +54,7 @@ where
|
||||
if algorithm != CompressionAlgorithm::Deflate {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("Compression algorithm {:?} not supported", algorithm),
|
||||
format!("Compression algorithm {algorithm:?} not supported"),
|
||||
));
|
||||
}
|
||||
let mut deflater = DeflateDecoder::new(payload.as_slice());
|
||||
@@ -102,7 +102,7 @@ struct HelloServer;
|
||||
#[tarpc::server]
|
||||
impl World for HelloServer {
|
||||
async fn hello(self, _: context::Context, name: String) -> String {
|
||||
format!("Hey, {}!", name)
|
||||
format!("Hey, {name}!")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use futures::future;
|
||||
use tarpc::context::Context;
|
||||
use tarpc::serde_transport as transport;
|
||||
use tarpc::server::{BaseChannel, Channel};
|
||||
use tarpc::tokio_serde::formats::Bincode;
|
||||
use tarpc::tokio_util::codec::length_delimited::LengthDelimitedCodec;
|
||||
use tokio::net::{UnixListener, UnixStream};
|
||||
use tokio_serde::formats::Bincode;
|
||||
use tokio_util::codec::length_delimited::LengthDelimitedCodec;
|
||||
|
||||
#[tarpc::service]
|
||||
pub trait PingService {
|
||||
@@ -14,16 +13,13 @@ pub trait PingService {
|
||||
#[derive(Clone)]
|
||||
struct Service;
|
||||
|
||||
#[tarpc::server]
|
||||
impl PingService for Service {
|
||||
type PingFut = future::Ready<()>;
|
||||
|
||||
fn ping(self, _: Context) -> Self::PingFut {
|
||||
future::ready(())
|
||||
}
|
||||
async fn ping(self, _: Context) {}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let bind_addr = "/tmp/tarpc_on_unix_example.sock";
|
||||
|
||||
let _ = std::fs::remove_file(bind_addr);
|
||||
@@ -46,5 +42,7 @@ async fn main() -> std::io::Result<()> {
|
||||
PingServiceClient::new(Default::default(), transport)
|
||||
.spawn()
|
||||
.ping(tarpc::context::current())
|
||||
.await
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -52,9 +52,9 @@ use tarpc::{
|
||||
client, context,
|
||||
serde_transport::tcp,
|
||||
server::{self, Channel},
|
||||
tokio_serde::formats::Json,
|
||||
};
|
||||
use tokio::net::ToSocketAddrs;
|
||||
use tokio_serde::formats::Json;
|
||||
use tracing::info;
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
@@ -129,7 +129,6 @@ impl Subscriber {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Subscription {
|
||||
subscriber: subscriber::SubscriberClient,
|
||||
topics: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -210,7 +209,6 @@ impl Publisher {
|
||||
self.clients.lock().unwrap().insert(
|
||||
subscriber_addr,
|
||||
Subscription {
|
||||
subscriber: subscriber.clone(),
|
||||
topics: topics.clone(),
|
||||
},
|
||||
);
|
||||
@@ -290,7 +288,7 @@ fn init_tracing(service_name: &str) -> anyhow::Result<()> {
|
||||
.install_batch(opentelemetry::runtime::Tokio)?;
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.with(tracing_subscriber::filter::EnvFilter::from_default_env())
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.with(tracing_opentelemetry::layer().with_tracer(tracer))
|
||||
.try_init()?;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use futures::future::{self, Ready};
|
||||
use std::io;
|
||||
use tarpc::{
|
||||
client, context,
|
||||
server::{self, Channel},
|
||||
@@ -30,12 +29,12 @@ impl World for HelloServer {
|
||||
type HelloFut = Ready<String>;
|
||||
|
||||
fn hello(self, _: context::Context, name: String) -> Self::HelloFut {
|
||||
future::ready(format!("Hello, {}!", name))
|
||||
future::ready(format!("Hello, {name}!"))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let (client_transport, server_transport) = tarpc::transport::channel::unbounded();
|
||||
|
||||
let server = server::BaseChannel::with_defaults(server_transport);
|
||||
@@ -50,7 +49,7 @@ async fn main() -> io::Result<()> {
|
||||
// specifies a deadline and trace information which can be helpful in debugging requests.
|
||||
let hello = client.hello(context::current(), "Stim".to_string()).await?;
|
||||
|
||||
println!("{}", hello);
|
||||
println!("{hello}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
152
tarpc/examples/tls_over_tcp.rs
Normal file
152
tarpc/examples/tls_over_tcp.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use rustls_pemfile::certs;
|
||||
use std::io::{BufReader, Cursor};
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use tokio_rustls::rustls::server::AllowAnyAuthenticatedClient;
|
||||
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_rustls::rustls::{self, Certificate, OwnedTrustAnchor, RootCertStore};
|
||||
use tokio_rustls::{webpki, TlsAcceptor, TlsConnector};
|
||||
|
||||
use tarpc::context::Context;
|
||||
use tarpc::serde_transport as transport;
|
||||
use tarpc::server::{BaseChannel, Channel};
|
||||
use tarpc::tokio_serde::formats::Bincode;
|
||||
use tarpc::tokio_util::codec::length_delimited::LengthDelimitedCodec;
|
||||
|
||||
#[tarpc::service]
|
||||
pub trait PingService {
|
||||
async fn ping() -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Service;
|
||||
|
||||
#[tarpc::server]
|
||||
impl PingService for Service {
|
||||
async fn ping(self, _: Context) -> String {
|
||||
"🔒".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
// certs were generated with openssl 3 https://github.com/rustls/rustls/tree/main/test-ca
|
||||
// used on client-side for server tls
|
||||
const END_CHAIN: &[u8] = include_bytes!("certs/eddsa/end.chain");
|
||||
// used on client-side for client-auth
|
||||
const CLIENT_PRIVATEKEY_CLIENT_AUTH: &str = include_str!("certs/eddsa/client.key");
|
||||
const CLIENT_CERT_CLIENT_AUTH: &str = include_str!("certs/eddsa/client.cert");
|
||||
|
||||
// used on server-side for server tls
|
||||
const END_CERT: &str = include_str!("certs/eddsa/end.cert");
|
||||
const END_PRIVATEKEY: &str = include_str!("certs/eddsa/end.key");
|
||||
// used on server-side for client-auth
|
||||
const CLIENT_CHAIN_CLIENT_AUTH: &str = include_str!("certs/eddsa/client.chain");
|
||||
|
||||
pub fn load_private_key(key: &str) -> rustls::PrivateKey {
|
||||
let mut reader = BufReader::new(Cursor::new(key));
|
||||
loop {
|
||||
match rustls_pemfile::read_one(&mut reader).expect("cannot parse private key .pem file") {
|
||||
Some(rustls_pemfile::Item::RSAKey(key)) => return rustls::PrivateKey(key),
|
||||
Some(rustls_pemfile::Item::PKCS8Key(key)) => return rustls::PrivateKey(key),
|
||||
Some(rustls_pemfile::Item::ECKey(key)) => return rustls::PrivateKey(key),
|
||||
None => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
panic!("no keys found in {:?} (encrypted keys not supported)", key);
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// -------------------- start here to setup tls tcp tokio stream --------------------------
|
||||
// ref certs and loading from: https://github.com/tokio-rs/tls/blob/master/tokio-rustls/tests/test.rs
|
||||
// ref basic tls server setup from: https://github.com/tokio-rs/tls/blob/master/tokio-rustls/examples/server/src/main.rs
|
||||
let cert = certs(&mut BufReader::new(Cursor::new(END_CERT)))
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(rustls::Certificate)
|
||||
.collect();
|
||||
let key = load_private_key(END_PRIVATEKEY);
|
||||
let server_addr = (IpAddr::V4(Ipv4Addr::LOCALHOST), 5000);
|
||||
|
||||
// ------------- server side client_auth cert loading start
|
||||
let roots: Vec<Certificate> = certs(&mut BufReader::new(Cursor::new(CLIENT_CHAIN_CLIENT_AUTH)))
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(rustls::Certificate)
|
||||
.collect();
|
||||
let mut client_auth_roots = RootCertStore::empty();
|
||||
for root in roots {
|
||||
client_auth_roots.add(&root).unwrap();
|
||||
}
|
||||
let client_auth = AllowAnyAuthenticatedClient::new(client_auth_roots);
|
||||
// ------------- server side client_auth cert loading end
|
||||
|
||||
let config = rustls::ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_client_cert_verifier(client_auth) // use .with_no_client_auth() instead if you don't want client-auth
|
||||
.with_single_cert(cert, key)
|
||||
.unwrap();
|
||||
let acceptor = TlsAcceptor::from(Arc::new(config));
|
||||
let listener = TcpListener::bind(&server_addr).await.unwrap();
|
||||
let codec_builder = LengthDelimitedCodec::builder();
|
||||
|
||||
// ref ./custom_transport.rs server side
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let (stream, _peer_addr) = listener.accept().await.unwrap();
|
||||
let acceptor = acceptor.clone();
|
||||
let tls_stream = acceptor.accept(stream).await.unwrap();
|
||||
let framed = codec_builder.new_framed(tls_stream);
|
||||
|
||||
let transport = transport::new(framed, Bincode::default());
|
||||
|
||||
let fut = BaseChannel::with_defaults(transport).execute(Service.serve());
|
||||
tokio::spawn(fut);
|
||||
}
|
||||
});
|
||||
|
||||
// ---------------------- client connection ---------------------
|
||||
// cert loading from: https://github.com/tokio-rs/tls/blob/357bc562483dcf04c1f8d08bd1a831b144bf7d4c/tokio-rustls/tests/test.rs#L113
|
||||
// tls client connection from https://github.com/tokio-rs/tls/blob/master/tokio-rustls/examples/client/src/main.rs
|
||||
let chain = certs(&mut std::io::Cursor::new(END_CHAIN)).unwrap();
|
||||
let mut root_store = rustls::RootCertStore::empty();
|
||||
root_store.add_server_trust_anchors(chain.iter().map(|cert| {
|
||||
let ta = webpki::TrustAnchor::try_from_cert_der(&cert[..]).unwrap();
|
||||
OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject,
|
||||
ta.spki,
|
||||
ta.name_constraints,
|
||||
)
|
||||
}));
|
||||
|
||||
let client_auth_private_key = load_private_key(CLIENT_PRIVATEKEY_CLIENT_AUTH);
|
||||
let client_auth_certs: Vec<Certificate> =
|
||||
certs(&mut BufReader::new(Cursor::new(CLIENT_CERT_CLIENT_AUTH)))
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(rustls::Certificate)
|
||||
.collect();
|
||||
|
||||
let config = rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(root_store)
|
||||
.with_single_cert(client_auth_certs, client_auth_private_key)?; // use .with_no_client_auth() instead if you don't want client-auth
|
||||
|
||||
let domain = rustls::ServerName::try_from("localhost")?;
|
||||
let connector = TlsConnector::from(Arc::new(config));
|
||||
|
||||
let stream = TcpStream::connect(server_addr).await?;
|
||||
let stream = connector.connect(domain, stream).await?;
|
||||
|
||||
let transport = transport::new(codec_builder.new_framed(stream), Bincode::default());
|
||||
let answer = PingServiceClient::new(Default::default(), transport)
|
||||
.spawn()
|
||||
.ping(tarpc::context::current())
|
||||
.await?;
|
||||
|
||||
println!("ping answer: {answer}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -6,12 +6,11 @@
|
||||
|
||||
use crate::{add::Add as AddService, double::Double as DoubleService};
|
||||
use futures::{future, prelude::*};
|
||||
use std::env;
|
||||
use tarpc::{
|
||||
client, context,
|
||||
server::{BaseChannel, Incoming},
|
||||
server::{incoming::Incoming, BaseChannel},
|
||||
tokio_serde::formats::Json,
|
||||
};
|
||||
use tokio_serde::formats::Json;
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
pub mod add {
|
||||
@@ -56,9 +55,9 @@ impl DoubleService for DoubleServer {
|
||||
}
|
||||
|
||||
fn init_tracing(service_name: &str) -> anyhow::Result<()> {
|
||||
env::set_var("OTEL_BSP_MAX_EXPORT_BATCH_SIZE", "12");
|
||||
let tracer = opentelemetry_jaeger::new_pipeline()
|
||||
.with_service_name(service_name)
|
||||
.with_auto_split_batch(true)
|
||||
.with_max_packet_size(2usize.pow(13))
|
||||
.install_batch(opentelemetry::runtime::Tokio)?;
|
||||
|
||||
|
||||
49
tarpc/src/cancellations.rs
Normal file
49
tarpc/src/cancellations.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use futures::{prelude::*, task::*};
|
||||
use std::pin::Pin;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// Sends request cancellation signals.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RequestCancellation(mpsc::UnboundedSender<u64>);
|
||||
|
||||
/// A stream of IDs of requests that have been canceled.
|
||||
#[derive(Debug)]
|
||||
pub struct CanceledRequests(mpsc::UnboundedReceiver<u64>);
|
||||
|
||||
/// Returns a channel to send request cancellation messages.
|
||||
pub fn cancellations() -> (RequestCancellation, CanceledRequests) {
|
||||
// Unbounded because messages are sent in the drop fn. This is fine, because it's still
|
||||
// bounded by the number of in-flight requests.
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
(RequestCancellation(tx), CanceledRequests(rx))
|
||||
}
|
||||
|
||||
impl RequestCancellation {
|
||||
/// Cancels the request with ID `request_id`.
|
||||
///
|
||||
/// No validation is done of `request_id`. There is no way to know if the request id provided
|
||||
/// corresponds to a request actually tracked by the backing channel. `RequestCancellation` is
|
||||
/// a one-way communication channel.
|
||||
///
|
||||
/// Once request data is cleaned up, a response will never be received by the client. This is
|
||||
/// useful primarily when request processing ends prematurely for requests with long deadlines
|
||||
/// which would otherwise continue to be tracked by the backing channel—a kind of leak.
|
||||
pub fn cancel(&self, request_id: u64) {
|
||||
let _ = self.0.send(request_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl CanceledRequests {
|
||||
/// Polls for a cancelled request.
|
||||
pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<u64>> {
|
||||
self.0.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for CanceledRequests {
|
||||
type Item = u64;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<u64>> {
|
||||
self.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,16 @@
|
||||
|
||||
mod in_flight_requests;
|
||||
|
||||
use crate::{context, trace, ClientMessage, Request, Response, Transport};
|
||||
use crate::{
|
||||
cancellations::{cancellations, CanceledRequests, RequestCancellation},
|
||||
context, trace, ChannelError, ClientMessage, Request, Response, ServerError, Transport,
|
||||
};
|
||||
use futures::{prelude::*, ready, stream::Fuse, task::*};
|
||||
use in_flight_requests::InFlightRequests;
|
||||
use pin_project::pin_project;
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
error::Error,
|
||||
fmt, io, mem,
|
||||
fmt,
|
||||
pin::Pin,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
@@ -60,15 +62,16 @@ pub struct NewClient<C, D> {
|
||||
impl<C, D, E> NewClient<C, D>
|
||||
where
|
||||
D: Future<Output = Result<(), E>> + Send + 'static,
|
||||
E: std::fmt::Display,
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
/// Helper method to spawn the dispatch on the default executor.
|
||||
#[cfg(feature = "tokio1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tokio1")))]
|
||||
pub fn spawn(self) -> C {
|
||||
let dispatch = self
|
||||
.dispatch
|
||||
.unwrap_or_else(move |e| tracing::warn!("Connection broken: {}", e));
|
||||
let dispatch = self.dispatch.unwrap_or_else(move |e| {
|
||||
let e = anyhow::Error::new(e);
|
||||
tracing::warn!("Connection broken: {:?}", e);
|
||||
});
|
||||
tokio::spawn(dispatch);
|
||||
self.client
|
||||
}
|
||||
@@ -80,14 +83,10 @@ impl<C, D> fmt::Debug for NewClient<C, D> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(clippy::no_effect)]
|
||||
const CHECK_USIZE: () = {
|
||||
if std::mem::size_of::<usize>() > std::mem::size_of::<u64>() {
|
||||
// TODO: replace this with panic!() as soon as RFC 2345 gets stabilized
|
||||
["usize is too big to fit in u64"][42];
|
||||
}
|
||||
};
|
||||
const _CHECK_USIZE: () = assert!(
|
||||
std::mem::size_of::<usize>() <= std::mem::size_of::<u64>(),
|
||||
"usize is too big to fit in u64"
|
||||
);
|
||||
|
||||
/// Handles communication from the client to request dispatch.
|
||||
#[derive(Debug)]
|
||||
@@ -117,18 +116,19 @@ impl<Req, Resp> Channel<Req, Resp> {
|
||||
skip(self, ctx, request_name, request),
|
||||
fields(
|
||||
rpc.trace_id = tracing::field::Empty,
|
||||
rpc.deadline = %humantime::format_rfc3339(ctx.deadline),
|
||||
otel.kind = "client",
|
||||
otel.name = request_name)
|
||||
)]
|
||||
pub async fn call(
|
||||
&self,
|
||||
mut ctx: context::Context,
|
||||
request_name: &str,
|
||||
request_name: &'static str,
|
||||
request: Req,
|
||||
) -> io::Result<Resp> {
|
||||
) -> Result<Resp, RpcError> {
|
||||
let span = Span::current();
|
||||
ctx.trace_context = trace::Context::try_from(&span).unwrap_or_else(|_| {
|
||||
tracing::warn!(
|
||||
tracing::trace!(
|
||||
"OpenTelemetry subscriber not installed; making unsampled child context."
|
||||
);
|
||||
ctx.trace_context.new_child()
|
||||
@@ -146,6 +146,7 @@ impl<Req, Resp> Channel<Req, Resp> {
|
||||
response: &mut response,
|
||||
request_id,
|
||||
cancellation: &self.cancellation,
|
||||
cancel: true,
|
||||
};
|
||||
self.to_dispatch
|
||||
.send(DispatchRequest {
|
||||
@@ -156,7 +157,7 @@ impl<Req, Resp> Channel<Req, Resp> {
|
||||
response_completion,
|
||||
})
|
||||
.await
|
||||
.map_err(|mpsc::error::SendError(_)| io::Error::from(io::ErrorKind::ConnectionReset))?;
|
||||
.map_err(|mpsc::error::SendError(_)| RpcError::Shutdown)?;
|
||||
response_guard.response().await
|
||||
}
|
||||
}
|
||||
@@ -164,23 +165,45 @@ impl<Req, Resp> Channel<Req, Resp> {
|
||||
/// A server response that is completed by request dispatch when the corresponding response
|
||||
/// arrives off the wire.
|
||||
struct ResponseGuard<'a, Resp> {
|
||||
response: &'a mut oneshot::Receiver<Response<Resp>>,
|
||||
response: &'a mut oneshot::Receiver<Result<Resp, RpcError>>,
|
||||
cancellation: &'a RequestCancellation,
|
||||
request_id: u64,
|
||||
cancel: bool,
|
||||
}
|
||||
|
||||
/// An error that can occur in the processing of an RPC. This is not request-specific errors but
|
||||
/// rather cross-cutting errors that can always occur.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum RpcError {
|
||||
/// The client disconnected from the server.
|
||||
#[error("the connection to the server was already shutdown")]
|
||||
Shutdown,
|
||||
/// The client failed to send the request.
|
||||
#[error("the client failed to send the request")]
|
||||
Send(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
/// An error occurred while waiting for the server response.
|
||||
#[error("an error occurred while waiting for the server response")]
|
||||
Receive(#[source] Arc<dyn std::error::Error + Send + Sync + 'static>),
|
||||
/// The request exceeded its deadline.
|
||||
#[error("the request exceeded its deadline")]
|
||||
DeadlineExceeded,
|
||||
/// The server aborted request processing.
|
||||
#[error("the server aborted request processing")]
|
||||
Server(#[from] ServerError),
|
||||
}
|
||||
|
||||
impl<Resp> ResponseGuard<'_, Resp> {
|
||||
async fn response(mut self) -> io::Result<Resp> {
|
||||
async fn response(mut self) -> Result<Resp, RpcError> {
|
||||
let response = (&mut self.response).await;
|
||||
// Cancel drop logic once a response has been received.
|
||||
mem::forget(self);
|
||||
self.cancel = false;
|
||||
match response {
|
||||
Ok(resp) => Ok(resp.message?),
|
||||
Ok(response) => response,
|
||||
Err(oneshot::error::RecvError { .. }) => {
|
||||
// The oneshot is Canceled when the dispatch task ends. In that case,
|
||||
// there's nothing listening on the other side, so there's no point in
|
||||
// propagating cancellation.
|
||||
Err(io::Error::from(io::ErrorKind::ConnectionReset))
|
||||
Err(RpcError::Shutdown)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +223,9 @@ impl<Resp> Drop for ResponseGuard<'_, Resp> {
|
||||
// dispatch task misses an early-arriving cancellation message, then it will see the
|
||||
// receiver as closed.
|
||||
self.response.close();
|
||||
self.cancellation.cancel(self.request_id);
|
||||
if self.cancel {
|
||||
self.cancellation.cancel(self.request_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +240,6 @@ where
|
||||
{
|
||||
let (to_dispatch, pending_requests) = mpsc::channel(config.pending_request_buffer);
|
||||
let (cancellation, canceled_requests) = cancellations();
|
||||
let canceled_requests = canceled_requests;
|
||||
|
||||
NewClient {
|
||||
client: Channel {
|
||||
@@ -235,6 +259,7 @@ where
|
||||
|
||||
/// Handles the lifecycle of requests, writing requests to the wire, managing cancellations,
|
||||
/// and dispatching responses to the appropriate channel.
|
||||
#[must_use]
|
||||
#[pin_project]
|
||||
#[derive(Debug)]
|
||||
pub struct RequestDispatch<Req, Resp, C> {
|
||||
@@ -246,30 +271,18 @@ pub struct RequestDispatch<Req, Resp, C> {
|
||||
/// Requests that were dropped.
|
||||
canceled_requests: CanceledRequests,
|
||||
/// Requests already written to the wire that haven't yet received responses.
|
||||
in_flight_requests: InFlightRequests<Resp>,
|
||||
in_flight_requests: InFlightRequests<Result<Resp, RpcError>>,
|
||||
/// Configures limits to prevent unlimited resource usage.
|
||||
config: Config,
|
||||
}
|
||||
|
||||
/// Critical errors that result in a Channel disconnecting.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ChannelError<E>
|
||||
where
|
||||
E: Error + Send + Sync + 'static,
|
||||
{
|
||||
/// An error occurred reading from, or writing to, the transport.
|
||||
#[error("an error occurred in the transport: {0}")]
|
||||
Transport(#[source] E),
|
||||
/// An error occurred while polling expired requests.
|
||||
#[error("an error occurred while polling expired requests: {0}")]
|
||||
Timer(#[source] tokio::time::error::Error),
|
||||
}
|
||||
|
||||
impl<Req, Resp, C> RequestDispatch<Req, Resp, C>
|
||||
where
|
||||
C: Transport<ClientMessage<Req>, Response<Resp>>,
|
||||
{
|
||||
fn in_flight_requests<'a>(self: &'a mut Pin<&mut Self>) -> &'a mut InFlightRequests<Resp> {
|
||||
fn in_flight_requests<'a>(
|
||||
self: &'a mut Pin<&mut Self>,
|
||||
) -> &'a mut InFlightRequests<Result<Resp, RpcError>> {
|
||||
self.as_mut().project().in_flight_requests
|
||||
}
|
||||
|
||||
@@ -277,6 +290,42 @@ where
|
||||
self.as_mut().project().transport
|
||||
}
|
||||
|
||||
fn poll_ready<'a>(
|
||||
self: &'a mut Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), ChannelError<C::Error>>> {
|
||||
self.transport_pin_mut()
|
||||
.poll_ready(cx)
|
||||
.map_err(ChannelError::Ready)
|
||||
}
|
||||
|
||||
fn start_send(
|
||||
self: &mut Pin<&mut Self>,
|
||||
message: ClientMessage<Req>,
|
||||
) -> Result<(), ChannelError<C::Error>> {
|
||||
self.transport_pin_mut()
|
||||
.start_send(message)
|
||||
.map_err(ChannelError::Write)
|
||||
}
|
||||
|
||||
fn poll_flush<'a>(
|
||||
self: &'a mut Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), ChannelError<C::Error>>> {
|
||||
self.transport_pin_mut()
|
||||
.poll_flush(cx)
|
||||
.map_err(ChannelError::Flush)
|
||||
}
|
||||
|
||||
fn poll_close<'a>(
|
||||
self: &'a mut Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), ChannelError<C::Error>>> {
|
||||
self.transport_pin_mut()
|
||||
.poll_close(cx)
|
||||
.map_err(ChannelError::Close)
|
||||
}
|
||||
|
||||
fn canceled_requests_mut<'a>(self: &'a mut Pin<&mut Self>) -> &'a mut CanceledRequests {
|
||||
self.as_mut().project().canceled_requests
|
||||
}
|
||||
@@ -293,7 +342,17 @@ where
|
||||
) -> Poll<Option<Result<(), ChannelError<C::Error>>>> {
|
||||
self.transport_pin_mut()
|
||||
.poll_next(cx)
|
||||
.map_err(ChannelError::Transport)
|
||||
.map_err(|e| {
|
||||
let e = Arc::new(e);
|
||||
for span in self
|
||||
.in_flight_requests()
|
||||
.complete_all_requests(|| Err(RpcError::Receive(e.clone())))
|
||||
{
|
||||
let _entered = span.enter();
|
||||
tracing::info!("ReceiveError");
|
||||
}
|
||||
ChannelError::Read(e)
|
||||
})
|
||||
.map_ok(|response| {
|
||||
self.complete(response);
|
||||
})
|
||||
@@ -308,21 +367,13 @@ where
|
||||
Closed,
|
||||
}
|
||||
|
||||
let pending_requests_status = match self
|
||||
.as_mut()
|
||||
.poll_write_request(cx)
|
||||
.map_err(ChannelError::Transport)?
|
||||
{
|
||||
let pending_requests_status = match self.as_mut().poll_write_request(cx)? {
|
||||
Poll::Ready(Some(())) => return Poll::Ready(Some(Ok(()))),
|
||||
Poll::Ready(None) => ReceiverStatus::Closed,
|
||||
Poll::Pending => ReceiverStatus::Pending,
|
||||
};
|
||||
|
||||
let canceled_requests_status = match self
|
||||
.as_mut()
|
||||
.poll_write_cancel(cx)
|
||||
.map_err(ChannelError::Transport)?
|
||||
{
|
||||
let canceled_requests_status = match self.as_mut().poll_write_cancel(cx)? {
|
||||
Poll::Ready(Some(())) => return Poll::Ready(Some(Ok(()))),
|
||||
Poll::Ready(None) => ReceiverStatus::Closed,
|
||||
Poll::Pending => ReceiverStatus::Pending,
|
||||
@@ -333,8 +384,7 @@ where
|
||||
// track the status like is done with pending and cancelled requests.
|
||||
if let Poll::Ready(Some(_)) = self
|
||||
.in_flight_requests()
|
||||
.poll_expired(cx)
|
||||
.map_err(ChannelError::Timer)?
|
||||
.poll_expired(cx, || Err(RpcError::DeadlineExceeded))
|
||||
{
|
||||
// Expired requests are considered complete; there is no compelling reason to send a
|
||||
// cancellation message to the server, since it will have already exhausted its
|
||||
@@ -344,18 +394,12 @@ where
|
||||
|
||||
match (pending_requests_status, canceled_requests_status) {
|
||||
(ReceiverStatus::Closed, ReceiverStatus::Closed) => {
|
||||
ready!(self
|
||||
.transport_pin_mut()
|
||||
.poll_flush(cx)
|
||||
.map_err(ChannelError::Transport)?);
|
||||
ready!(self.poll_close(cx)?);
|
||||
Poll::Ready(None)
|
||||
}
|
||||
(ReceiverStatus::Pending, _) | (_, ReceiverStatus::Pending) => {
|
||||
// No more messages to process, so flush any messages buffered in the transport.
|
||||
ready!(self
|
||||
.transport_pin_mut()
|
||||
.poll_flush(cx)
|
||||
.map_err(ChannelError::Transport)?);
|
||||
ready!(self.poll_flush(cx)?);
|
||||
|
||||
// Even if we fully-flush, we return Pending, because we have no more requests
|
||||
// or cancellations right now.
|
||||
@@ -371,7 +415,7 @@ where
|
||||
fn poll_next_request(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<DispatchRequest<Req, Resp>, C::Error>>> {
|
||||
) -> Poll<Option<Result<DispatchRequest<Req, Resp>, ChannelError<C::Error>>>> {
|
||||
if self.in_flight_requests().len() >= self.config.max_in_flight_requests {
|
||||
tracing::info!(
|
||||
"At in-flight request capacity ({}/{}).",
|
||||
@@ -409,7 +453,7 @@ where
|
||||
fn poll_next_cancellation(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<(context::Context, Span, u64), C::Error>>> {
|
||||
) -> Poll<Option<Result<(context::Context, Span, u64), ChannelError<C::Error>>>> {
|
||||
ready!(self.ensure_writeable(cx)?);
|
||||
|
||||
loop {
|
||||
@@ -431,9 +475,9 @@ where
|
||||
fn ensure_writeable<'a>(
|
||||
self: &'a mut Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<(), C::Error>>> {
|
||||
while self.transport_pin_mut().poll_ready(cx)?.is_pending() {
|
||||
ready!(self.transport_pin_mut().poll_flush(cx)?);
|
||||
) -> Poll<Option<Result<(), ChannelError<C::Error>>>> {
|
||||
while self.poll_ready(cx)?.is_pending() {
|
||||
ready!(self.poll_flush(cx)?);
|
||||
}
|
||||
Poll::Ready(Some(Ok(())))
|
||||
}
|
||||
@@ -441,7 +485,7 @@ where
|
||||
fn poll_write_request<'a>(
|
||||
self: &'a mut Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<(), C::Error>>> {
|
||||
) -> Poll<Option<Result<(), ChannelError<C::Error>>>> {
|
||||
let DispatchRequest {
|
||||
ctx,
|
||||
span,
|
||||
@@ -452,7 +496,7 @@ where
|
||||
Some(dispatch_request) => dispatch_request,
|
||||
None => return Poll::Ready(None),
|
||||
};
|
||||
let entered = span.enter();
|
||||
let _entered = span.enter();
|
||||
// poll_next_request only returns Ready if there is room to buffer another request.
|
||||
// Therefore, we can call write_request without fear of erroring due to a full
|
||||
// buffer.
|
||||
@@ -465,24 +509,23 @@ where
|
||||
trace_context: ctx.trace_context,
|
||||
},
|
||||
});
|
||||
self.transport_pin_mut().start_send(request)?;
|
||||
let deadline = ctx.deadline;
|
||||
tracing::info!(
|
||||
tarpc.deadline = %humantime::format_rfc3339(deadline),
|
||||
"SendRequest"
|
||||
);
|
||||
drop(entered);
|
||||
|
||||
self.in_flight_requests()
|
||||
.insert_request(request_id, ctx, span, response_completion)
|
||||
.insert_request(request_id, ctx, span.clone(), response_completion)
|
||||
.expect("Request IDs should be unique");
|
||||
match self.start_send(request) {
|
||||
Ok(()) => tracing::info!("SendRequest"),
|
||||
Err(e) => {
|
||||
self.in_flight_requests()
|
||||
.complete_request(request_id, Err(RpcError::Send(Box::new(e))));
|
||||
}
|
||||
}
|
||||
Poll::Ready(Some(Ok(())))
|
||||
}
|
||||
|
||||
fn poll_write_cancel<'a>(
|
||||
self: &'a mut Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<(), C::Error>>> {
|
||||
) -> Poll<Option<Result<(), ChannelError<C::Error>>>> {
|
||||
let (context, span, request_id) = match ready!(self.as_mut().poll_next_cancellation(cx)?) {
|
||||
Some(triple) => triple,
|
||||
None => return Poll::Ready(None),
|
||||
@@ -493,14 +536,17 @@ where
|
||||
trace_context: context.trace_context,
|
||||
request_id,
|
||||
};
|
||||
self.transport_pin_mut().start_send(cancel)?;
|
||||
self.start_send(cancel)?;
|
||||
tracing::info!("CancelRequest");
|
||||
Poll::Ready(Some(Ok(())))
|
||||
}
|
||||
|
||||
/// Sends a server response to the client task that initiated the associated request.
|
||||
fn complete(mut self: Pin<&mut Self>, response: Response<Resp>) -> bool {
|
||||
self.in_flight_requests().complete_request(response)
|
||||
self.in_flight_requests().complete_request(
|
||||
response.request_id,
|
||||
response.message.map_err(RpcError::Server),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,73 +595,43 @@ struct DispatchRequest<Req, Resp> {
|
||||
pub span: Span,
|
||||
pub request_id: u64,
|
||||
pub request: Req,
|
||||
pub response_completion: oneshot::Sender<Response<Resp>>,
|
||||
}
|
||||
|
||||
/// Sends request cancellation signals.
|
||||
#[derive(Debug, Clone)]
|
||||
struct RequestCancellation(mpsc::UnboundedSender<u64>);
|
||||
|
||||
/// A stream of IDs of requests that have been canceled.
|
||||
#[derive(Debug)]
|
||||
struct CanceledRequests(mpsc::UnboundedReceiver<u64>);
|
||||
|
||||
/// Returns a channel to send request cancellation messages.
|
||||
fn cancellations() -> (RequestCancellation, CanceledRequests) {
|
||||
// Unbounded because messages are sent in the drop fn. This is fine, because it's still
|
||||
// bounded by the number of in-flight requests.
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
(RequestCancellation(tx), CanceledRequests(rx))
|
||||
}
|
||||
|
||||
impl RequestCancellation {
|
||||
/// Cancels the request with ID `request_id`.
|
||||
fn cancel(&self, request_id: u64) {
|
||||
let _ = self.0.send(request_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl CanceledRequests {
|
||||
fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<u64>> {
|
||||
self.0.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for CanceledRequests {
|
||||
type Item = u64;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<u64>> {
|
||||
self.poll_recv(cx)
|
||||
}
|
||||
pub response_completion: oneshot::Sender<Result<Resp, RpcError>>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
cancellations, CanceledRequests, Channel, DispatchRequest, RequestCancellation,
|
||||
RequestDispatch, ResponseGuard,
|
||||
cancellations, Channel, DispatchRequest, RequestDispatch, ResponseGuard, RpcError,
|
||||
};
|
||||
use crate::{
|
||||
client::{in_flight_requests::InFlightRequests, Config},
|
||||
context,
|
||||
context::{self, current},
|
||||
transport::{self, channel::UnboundedChannel},
|
||||
ClientMessage, Response,
|
||||
ChannelError, ClientMessage, Response,
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use futures::{prelude::*, task::*};
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
fmt::Display,
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
sync::Arc,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use thiserror::Error;
|
||||
use tokio::sync::{
|
||||
mpsc::{self},
|
||||
oneshot,
|
||||
};
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tracing::Span;
|
||||
|
||||
#[tokio::test]
|
||||
async fn response_completes_request_future() {
|
||||
let (mut dispatch, mut _channel, mut server_channel) = set_up();
|
||||
let cx = &mut Context::from_waker(&noop_waker_ref());
|
||||
let cx = &mut Context::from_waker(noop_waker_ref());
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
|
||||
dispatch
|
||||
@@ -630,7 +646,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
assert_matches!(dispatch.as_mut().poll(cx), Poll::Pending);
|
||||
assert_matches!(rx.try_recv(), Ok(Response { request_id: 0, message: Ok(resp) }) if resp == "Resp");
|
||||
assert_matches!(rx.try_recv(), Ok(Ok(resp)) if resp == "Resp");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -641,43 +657,46 @@ mod tests {
|
||||
response: &mut response,
|
||||
cancellation: &cancellation,
|
||||
request_id: 3,
|
||||
cancel: true,
|
||||
});
|
||||
// resp's drop() is run, which should send a cancel message.
|
||||
let cx = &mut Context::from_waker(&noop_waker_ref());
|
||||
assert_eq!(canceled_requests.0.poll_recv(cx), Poll::Ready(Some(3)));
|
||||
let cx = &mut Context::from_waker(noop_waker_ref());
|
||||
assert_eq!(canceled_requests.poll_recv(cx), Poll::Ready(Some(3)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dispatch_response_doesnt_cancel_after_complete() {
|
||||
let (cancellation, mut canceled_requests) = cancellations();
|
||||
let (tx, mut response) = oneshot::channel();
|
||||
tx.send(Response {
|
||||
tx.send(Ok(Response {
|
||||
request_id: 0,
|
||||
message: Ok("well done"),
|
||||
})
|
||||
}))
|
||||
.unwrap();
|
||||
// resp's drop() is run, but should not send a cancel message.
|
||||
ResponseGuard {
|
||||
response: &mut response,
|
||||
cancellation: &cancellation,
|
||||
request_id: 3,
|
||||
cancel: true,
|
||||
}
|
||||
.response()
|
||||
.await
|
||||
.unwrap();
|
||||
drop(cancellation);
|
||||
let cx = &mut Context::from_waker(&noop_waker_ref());
|
||||
assert_eq!(canceled_requests.0.poll_recv(cx), Poll::Ready(None));
|
||||
let cx = &mut Context::from_waker(noop_waker_ref());
|
||||
assert_eq!(canceled_requests.poll_recv(cx), Poll::Ready(None));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stage_request() {
|
||||
let (mut dispatch, mut channel, _server_channel) = set_up();
|
||||
let cx = &mut Context::from_waker(&noop_waker_ref());
|
||||
let cx = &mut Context::from_waker(noop_waker_ref());
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
|
||||
let _resp = send_request(&mut channel, "hi", tx, &mut rx).await;
|
||||
|
||||
#[allow(unstable_name_collisions)]
|
||||
let req = dispatch.as_mut().poll_next_request(cx).ready();
|
||||
assert!(req.is_some());
|
||||
|
||||
@@ -690,7 +709,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn stage_request_channel_dropped_doesnt_panic() {
|
||||
let (mut dispatch, mut channel, mut server_channel) = set_up();
|
||||
let cx = &mut Context::from_waker(&noop_waker_ref());
|
||||
let cx = &mut Context::from_waker(noop_waker_ref());
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
|
||||
let _ = send_request(&mut channel, "hi", tx, &mut rx).await;
|
||||
@@ -708,10 +727,11 @@ mod tests {
|
||||
dispatch.await.unwrap();
|
||||
}
|
||||
|
||||
#[allow(unstable_name_collisions)]
|
||||
#[tokio::test]
|
||||
async fn stage_request_response_future_dropped_is_canceled_before_sending() {
|
||||
let (mut dispatch, mut channel, _server_channel) = set_up();
|
||||
let cx = &mut Context::from_waker(&noop_waker_ref());
|
||||
let cx = &mut Context::from_waker(noop_waker_ref());
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
|
||||
let _ = send_request(&mut channel, "hi", tx, &mut rx).await;
|
||||
@@ -723,10 +743,11 @@ mod tests {
|
||||
assert!(dispatch.as_mut().poll_next_request(cx).ready().is_none());
|
||||
}
|
||||
|
||||
#[allow(unstable_name_collisions)]
|
||||
#[tokio::test]
|
||||
async fn stage_request_response_future_dropped_is_canceled_after_sending() {
|
||||
let (mut dispatch, mut channel, _server_channel) = set_up();
|
||||
let cx = &mut Context::from_waker(&noop_waker_ref());
|
||||
let cx = &mut Context::from_waker(noop_waker_ref());
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
|
||||
let req = send_request(&mut channel, "hi", tx, &mut rx).await;
|
||||
@@ -747,7 +768,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn stage_request_response_closed_skipped() {
|
||||
let (mut dispatch, mut channel, _server_channel) = set_up();
|
||||
let cx = &mut Context::from_waker(&noop_waker_ref());
|
||||
let cx = &mut Context::from_waker(noop_waker_ref());
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
|
||||
// Test that a request future that's closed its receiver but not yet canceled its request --
|
||||
@@ -759,6 +780,185 @@ mod tests {
|
||||
assert!(dispatch.as_mut().poll_next_request(cx).is_pending());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_shutdown_error() {
|
||||
let _ = tracing_subscriber::fmt().with_test_writer().try_init();
|
||||
let (dispatch, mut channel, _) = set_up();
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
// send succeeds
|
||||
let resp = send_request(&mut channel, "hi", tx, &mut rx).await;
|
||||
drop(dispatch);
|
||||
// error on receive
|
||||
assert_matches!(resp.response().await, Err(RpcError::Shutdown));
|
||||
let (dispatch, channel, _) = set_up();
|
||||
drop(dispatch);
|
||||
// error on send
|
||||
let resp = channel
|
||||
.call(current(), "test_request", "hi".to_string())
|
||||
.await;
|
||||
assert_matches!(resp, Err(RpcError::Shutdown));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_transport_error_write() {
|
||||
let cause = TransportError::Write;
|
||||
let (mut dispatch, mut channel, mut cx) = setup_always_err(cause);
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
|
||||
let resp = send_request(&mut channel, "hi", tx, &mut rx).await;
|
||||
assert!(dispatch.as_mut().poll(&mut cx).is_pending());
|
||||
let res = resp.response().await;
|
||||
assert_matches!(res, Err(RpcError::Send(_)));
|
||||
let client_error: anyhow::Error = res.unwrap_err().into();
|
||||
let mut chain = client_error.chain();
|
||||
chain.next(); // original RpcError
|
||||
assert_eq!(
|
||||
chain
|
||||
.next()
|
||||
.unwrap()
|
||||
.downcast_ref::<ChannelError<TransportError>>(),
|
||||
Some(&ChannelError::Write(cause))
|
||||
);
|
||||
assert_eq!(
|
||||
client_error.root_cause().downcast_ref::<TransportError>(),
|
||||
Some(&cause)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_transport_error_read() {
|
||||
let cause = TransportError::Read;
|
||||
let (mut dispatch, mut channel, mut cx) = setup_always_err(cause);
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
let resp = send_request(&mut channel, "hi", tx, &mut rx).await;
|
||||
assert_eq!(
|
||||
dispatch.as_mut().pump_write(&mut cx),
|
||||
Poll::Ready(Some(Ok(())))
|
||||
);
|
||||
assert_eq!(
|
||||
dispatch.as_mut().pump_read(&mut cx),
|
||||
Poll::Ready(Some(Err(ChannelError::Read(Arc::new(cause)))))
|
||||
);
|
||||
assert_matches!(resp.response().await, Err(RpcError::Receive(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_transport_error_ready() {
|
||||
let cause = TransportError::Ready;
|
||||
let (mut dispatch, _, mut cx) = setup_always_err(cause);
|
||||
assert_eq!(
|
||||
dispatch.as_mut().poll(&mut cx),
|
||||
Poll::Ready(Err(ChannelError::Ready(cause)))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_transport_error_flush() {
|
||||
let cause = TransportError::Flush;
|
||||
let (mut dispatch, _, mut cx) = setup_always_err(cause);
|
||||
assert_eq!(
|
||||
dispatch.as_mut().poll(&mut cx),
|
||||
Poll::Ready(Err(ChannelError::Flush(cause)))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_transport_error_close() {
|
||||
let cause = TransportError::Close;
|
||||
let (mut dispatch, channel, mut cx) = setup_always_err(cause);
|
||||
drop(channel);
|
||||
assert_eq!(
|
||||
dispatch.as_mut().poll(&mut cx),
|
||||
Poll::Ready(Err(ChannelError::Close(cause)))
|
||||
);
|
||||
}
|
||||
|
||||
fn setup_always_err(
|
||||
cause: TransportError,
|
||||
) -> (
|
||||
Pin<Box<RequestDispatch<String, String, AlwaysErrorTransport<String>>>>,
|
||||
Channel<String, String>,
|
||||
Context<'static>,
|
||||
) {
|
||||
let (to_dispatch, pending_requests) = mpsc::channel(1);
|
||||
let (cancellation, canceled_requests) = cancellations();
|
||||
let transport: AlwaysErrorTransport<String> = AlwaysErrorTransport(cause, PhantomData);
|
||||
let dispatch = Box::pin(RequestDispatch::<String, String, _> {
|
||||
transport: transport.fuse(),
|
||||
pending_requests,
|
||||
canceled_requests,
|
||||
in_flight_requests: InFlightRequests::default(),
|
||||
config: Config::default(),
|
||||
});
|
||||
let channel = Channel {
|
||||
to_dispatch,
|
||||
cancellation,
|
||||
next_request_id: Arc::new(AtomicUsize::new(0)),
|
||||
};
|
||||
let cx = Context::from_waker(noop_waker_ref());
|
||||
(dispatch, channel, cx)
|
||||
}
|
||||
|
||||
struct AlwaysErrorTransport<I>(TransportError, PhantomData<I>);
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq, Clone, Copy)]
|
||||
enum TransportError {
|
||||
Read,
|
||||
Ready,
|
||||
Write,
|
||||
Flush,
|
||||
Close,
|
||||
}
|
||||
|
||||
impl Display for TransportError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&format!("{self:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Clone, S> Sink<S> for AlwaysErrorTransport<I> {
|
||||
type Error = TransportError;
|
||||
fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
match self.0 {
|
||||
TransportError::Ready => Poll::Ready(Err(self.0)),
|
||||
TransportError::Flush => Poll::Pending,
|
||||
_ => Poll::Ready(Ok(())),
|
||||
}
|
||||
}
|
||||
fn start_send(self: Pin<&mut Self>, _: S) -> Result<(), Self::Error> {
|
||||
if matches!(self.0, TransportError::Write) {
|
||||
Err(self.0)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if matches!(self.0, TransportError::Flush) {
|
||||
Poll::Ready(Err(self.0))
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
if matches!(self.0, TransportError::Close) {
|
||||
Poll::Ready(Err(self.0))
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Clone> Stream for AlwaysErrorTransport<I> {
|
||||
type Item = Result<Response<I>, TransportError>;
|
||||
fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
if matches!(self.0, TransportError::Read) {
|
||||
Poll::Ready(Some(Err(self.0)))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_up() -> (
|
||||
Pin<
|
||||
Box<
|
||||
@@ -775,18 +975,17 @@ mod tests {
|
||||
let _ = tracing_subscriber::fmt().with_test_writer().try_init();
|
||||
|
||||
let (to_dispatch, pending_requests) = mpsc::channel(1);
|
||||
let (cancel_tx, canceled_requests) = mpsc::unbounded_channel();
|
||||
let (cancellation, canceled_requests) = cancellations();
|
||||
let (client_channel, server_channel) = transport::channel::unbounded();
|
||||
|
||||
let dispatch = RequestDispatch::<String, String, _> {
|
||||
transport: client_channel.fuse(),
|
||||
pending_requests: pending_requests,
|
||||
canceled_requests: CanceledRequests(canceled_requests),
|
||||
pending_requests,
|
||||
canceled_requests,
|
||||
in_flight_requests: InFlightRequests::default(),
|
||||
config: Config::default(),
|
||||
};
|
||||
|
||||
let cancellation = RequestCancellation(cancel_tx);
|
||||
let channel = Channel {
|
||||
to_dispatch,
|
||||
cancellation,
|
||||
@@ -799,8 +998,8 @@ mod tests {
|
||||
async fn send_request<'a>(
|
||||
channel: &'a mut Channel<String, String>,
|
||||
request: &str,
|
||||
response_completion: oneshot::Sender<Response<String>>,
|
||||
response: &'a mut oneshot::Receiver<Response<String>>,
|
||||
response_completion: oneshot::Sender<Result<String, RpcError>>,
|
||||
response: &'a mut oneshot::Receiver<Result<String, RpcError>>,
|
||||
) -> ResponseGuard<'a, String> {
|
||||
let request_id =
|
||||
u64::try_from(channel.next_request_id.fetch_add(1, Ordering::Relaxed)).unwrap();
|
||||
@@ -811,13 +1010,14 @@ mod tests {
|
||||
request: request.to_string(),
|
||||
response_completion,
|
||||
};
|
||||
channel.to_dispatch.send(request).await.unwrap();
|
||||
|
||||
ResponseGuard {
|
||||
let response_guard = ResponseGuard {
|
||||
response,
|
||||
cancellation: &channel.cancellation,
|
||||
request_id,
|
||||
}
|
||||
cancel: true,
|
||||
};
|
||||
channel.to_dispatch.send(request).await.unwrap();
|
||||
response_guard
|
||||
}
|
||||
|
||||
async fn send_response(
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
use crate::{
|
||||
context,
|
||||
util::{Compact, TimeUntil},
|
||||
Response, ServerError,
|
||||
};
|
||||
use fnv::FnvHashMap;
|
||||
use std::{
|
||||
collections::hash_map,
|
||||
io,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::sync::oneshot;
|
||||
@@ -30,10 +28,10 @@ impl<Resp> Default for InFlightRequests<Resp> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RequestData<Resp> {
|
||||
struct RequestData<Res> {
|
||||
ctx: context::Context,
|
||||
span: Span,
|
||||
response_completion: oneshot::Sender<Response<Resp>>,
|
||||
response_completion: oneshot::Sender<Res>,
|
||||
/// The key to remove the timer for the request's deadline.
|
||||
deadline_key: delay_queue::Key,
|
||||
}
|
||||
@@ -43,7 +41,7 @@ struct RequestData<Resp> {
|
||||
#[derive(Debug)]
|
||||
pub struct AlreadyExistsError;
|
||||
|
||||
impl<Resp> InFlightRequests<Resp> {
|
||||
impl<Res> InFlightRequests<Res> {
|
||||
/// Returns the number of in-flight requests.
|
||||
pub fn len(&self) -> usize {
|
||||
self.request_data.len()
|
||||
@@ -60,7 +58,7 @@ impl<Resp> InFlightRequests<Resp> {
|
||||
request_id: u64,
|
||||
ctx: context::Context,
|
||||
span: Span,
|
||||
response_completion: oneshot::Sender<Response<Resp>>,
|
||||
response_completion: oneshot::Sender<Res>,
|
||||
) -> Result<(), AlreadyExistsError> {
|
||||
match self.request_data.entry(request_id) {
|
||||
hash_map::Entry::Vacant(vacant) => {
|
||||
@@ -79,25 +77,35 @@ impl<Resp> InFlightRequests<Resp> {
|
||||
}
|
||||
|
||||
/// Removes a request without aborting. Returns true iff the request was found.
|
||||
pub fn complete_request(&mut self, response: Response<Resp>) -> bool {
|
||||
if let Some(request_data) = self.request_data.remove(&response.request_id) {
|
||||
pub fn complete_request(&mut self, request_id: u64, result: Res) -> bool {
|
||||
if let Some(request_data) = self.request_data.remove(&request_id) {
|
||||
let _entered = request_data.span.enter();
|
||||
tracing::info!("ReceiveResponse");
|
||||
self.request_data.compact(0.1);
|
||||
self.deadlines.remove(&request_data.deadline_key);
|
||||
let _ = request_data.response_completion.send(response);
|
||||
let _ = request_data.response_completion.send(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
"No in-flight request found for request_id = {}.",
|
||||
response.request_id
|
||||
);
|
||||
tracing::debug!("No in-flight request found for request_id = {request_id}.");
|
||||
|
||||
// If the response completion was absent, then the request was already canceled.
|
||||
false
|
||||
}
|
||||
|
||||
/// Completes all requests using the provided function.
|
||||
/// Returns Spans for all completes requests.
|
||||
pub fn complete_all_requests<'a>(
|
||||
&'a mut self,
|
||||
mut result: impl FnMut() -> Res + 'a,
|
||||
) -> impl Iterator<Item = Span> + 'a {
|
||||
self.deadlines.clear();
|
||||
self.request_data.drain().map(move |(_, request_data)| {
|
||||
let _ = request_data.response_completion.send(result());
|
||||
request_data.span
|
||||
})
|
||||
}
|
||||
|
||||
/// Cancels a request without completing (typically used when a request handle was dropped
|
||||
/// before the request completed).
|
||||
pub fn cancel_request(&mut self, request_id: u64) -> Option<(context::Context, Span)> {
|
||||
@@ -115,28 +123,17 @@ impl<Resp> InFlightRequests<Resp> {
|
||||
pub fn poll_expired(
|
||||
&mut self,
|
||||
cx: &mut Context,
|
||||
) -> Poll<Option<Result<u64, tokio::time::error::Error>>> {
|
||||
self.deadlines.poll_expired(cx).map_ok(|expired| {
|
||||
let request_id = expired.into_inner();
|
||||
expired_error: impl Fn() -> Res,
|
||||
) -> Poll<Option<u64>> {
|
||||
self.deadlines.poll_expired(cx).map(|expired| {
|
||||
let request_id = expired?.into_inner();
|
||||
if let Some(request_data) = self.request_data.remove(&request_id) {
|
||||
let _entered = request_data.span.enter();
|
||||
tracing::error!("DeadlineExceeded");
|
||||
self.request_data.compact(0.1);
|
||||
let _ = request_data
|
||||
.response_completion
|
||||
.send(Self::deadline_exceeded_error(request_id));
|
||||
let _ = request_data.response_completion.send(expired_error());
|
||||
}
|
||||
request_id
|
||||
Some(request_id)
|
||||
})
|
||||
}
|
||||
|
||||
fn deadline_exceeded_error(request_id: u64) -> Response<Resp> {
|
||||
Response {
|
||||
request_id,
|
||||
message: Err(ServerError {
|
||||
kind: io::ErrorKind::TimedOut,
|
||||
detail: Some("Client dropped expired request.".to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ pub struct Context {
|
||||
/// When the client expects the request to be complete by. The server should cancel the request
|
||||
/// if it is not complete by this time.
|
||||
#[cfg_attr(feature = "serde1", serde(default = "ten_seconds_from_now"))]
|
||||
// Serialized as a Duration to prevent clock skew issues.
|
||||
#[cfg_attr(feature = "serde1", serde(with = "absolute_to_relative_time"))]
|
||||
pub deadline: SystemTime,
|
||||
/// Uniquely identifies requests originating from the same source.
|
||||
/// When a service handles a request by making requests itself, those requests should
|
||||
@@ -36,6 +38,54 @@ pub struct Context {
|
||||
pub trace_context: trace::Context,
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde1")]
|
||||
mod absolute_to_relative_time {
|
||||
pub use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
pub use std::time::{Duration, SystemTime};
|
||||
|
||||
pub fn serialize<S>(deadline: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let deadline = deadline
|
||||
.duration_since(SystemTime::now())
|
||||
.unwrap_or(Duration::ZERO);
|
||||
deadline.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let deadline = Duration::deserialize(deserializer)?;
|
||||
Ok(SystemTime::now() + deadline)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct AbsoluteToRelative(#[serde(with = "self")] SystemTime);
|
||||
|
||||
#[test]
|
||||
fn test_serialize() {
|
||||
let now = SystemTime::now();
|
||||
let deadline = now + Duration::from_secs(10);
|
||||
let serialized_deadline = bincode::serialize(&AbsoluteToRelative(deadline)).unwrap();
|
||||
let deserialized_deadline: Duration = bincode::deserialize(&serialized_deadline).unwrap();
|
||||
// TODO: how to avoid flakiness?
|
||||
assert!(deserialized_deadline > Duration::from_secs(9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize() {
|
||||
let deadline = Duration::from_secs(10);
|
||||
let serialized_deadline = bincode::serialize(&deadline).unwrap();
|
||||
let AbsoluteToRelative(deserialized_deadline) =
|
||||
bincode::deserialize(&serialized_deadline).unwrap();
|
||||
// TODO: how to avoid flakiness?
|
||||
assert!(deserialized_deadline > SystemTime::now() + Duration::from_secs(9));
|
||||
}
|
||||
}
|
||||
|
||||
assert_impl_all!(Context: Send, Sync);
|
||||
|
||||
fn ten_seconds_from_now() -> SystemTime {
|
||||
@@ -92,7 +142,7 @@ impl SpanExt for tracing::Span {
|
||||
.with_remote_span_context(opentelemetry::trace::SpanContext::new(
|
||||
opentelemetry::trace::TraceId::from(context.trace_context.trace_id),
|
||||
opentelemetry::trace::SpanId::from(context.trace_context.span_id),
|
||||
context.trace_context.sampling_decision as u8,
|
||||
opentelemetry::trace::TraceFlags::from(context.trace_context.sampling_decision),
|
||||
true,
|
||||
opentelemetry::trace::TraceState::default(),
|
||||
))
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
//! process, and no context switching between different languages.
|
||||
//!
|
||||
//! Some other features of tarpc:
|
||||
//! - Pluggable transport: any type impling `Stream<Item = Request> + Sink<Response>` can be
|
||||
//! - Pluggable transport: any type implementing `Stream<Item = Request> + Sink<Response>` can be
|
||||
//! used as a transport to connect the client and server.
|
||||
//! - `Send + 'static` optional: if the transport doesn't require it, neither does tarpc!
|
||||
//! - Cascading cancellation: dropping a request will send a cancellation message to the server.
|
||||
@@ -42,7 +42,7 @@
|
||||
//! [tracing](https://github.com/tokio-rs/tracing) primitives extended with
|
||||
//! [OpenTelemetry](https://opentelemetry.io/) traces. Using a compatible tracing subscriber like
|
||||
//! [Jaeger](https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-jaeger),
|
||||
//! each RPC can be traced through the client, server, amd other dependencies downstream of the
|
||||
//! each RPC can be traced through the client, server, and other dependencies downstream of the
|
||||
//! server. Even for applications not connected to a distributed tracing collector, the
|
||||
//! instrumentation can also be ingested by regular loggers like
|
||||
//! [env_logger](https://github.com/env-logger-rs/env_logger/).
|
||||
@@ -54,7 +54,7 @@
|
||||
//! Add to your `Cargo.toml` dependencies:
|
||||
//!
|
||||
//! ```toml
|
||||
//! tarpc = "0.26"
|
||||
//! tarpc = "0.29"
|
||||
//! ```
|
||||
//!
|
||||
//! The `tarpc::service` attribute expands to a collection of items that form an rpc service.
|
||||
@@ -67,8 +67,9 @@
|
||||
//! your `Cargo.toml`:
|
||||
//!
|
||||
//! ```toml
|
||||
//! futures = "1.0"
|
||||
//! tarpc = { version = "0.26", features = ["tokio1"] }
|
||||
//! anyhow = "1.0"
|
||||
//! futures = "0.3"
|
||||
//! tarpc = { version = "0.29", features = ["tokio1"] }
|
||||
//! tokio = { version = "1.0", features = ["macros"] }
|
||||
//! ```
|
||||
//!
|
||||
@@ -87,9 +88,8 @@
|
||||
//! };
|
||||
//! use tarpc::{
|
||||
//! client, context,
|
||||
//! server::{self, Incoming},
|
||||
//! server::{self, incoming::Incoming, Channel},
|
||||
//! };
|
||||
//! use std::io;
|
||||
//!
|
||||
//! // This is the service definition. It looks a lot like a trait definition.
|
||||
//! // It defines one RPC, hello, which takes one arg, name, and returns a String.
|
||||
@@ -111,9 +111,8 @@
|
||||
//! # };
|
||||
//! # use tarpc::{
|
||||
//! # client, context,
|
||||
//! # server::{self, Incoming},
|
||||
//! # server::{self, incoming::Incoming},
|
||||
//! # };
|
||||
//! # use std::io;
|
||||
//! # // This is the service definition. It looks a lot like a trait definition.
|
||||
//! # // It defines one RPC, hello, which takes one arg, name, and returns a String.
|
||||
//! # #[tarpc::service]
|
||||
@@ -133,7 +132,7 @@
|
||||
//! type HelloFut = Ready<String>;
|
||||
//!
|
||||
//! fn hello(self, _: context::Context, name: String) -> Self::HelloFut {
|
||||
//! future::ready(format!("Hello, {}!", name))
|
||||
//! future::ready(format!("Hello, {name}!"))
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
@@ -153,7 +152,6 @@
|
||||
//! # client, context,
|
||||
//! # server::{self, Channel},
|
||||
//! # };
|
||||
//! # use std::io;
|
||||
//! # // This is the service definition. It looks a lot like a trait definition.
|
||||
//! # // It defines one RPC, hello, which takes one arg, name, and returns a String.
|
||||
//! # #[tarpc::service]
|
||||
@@ -170,14 +168,14 @@
|
||||
//! # // an associated type representing the future output by the fn.
|
||||
//! # type HelloFut = Ready<String>;
|
||||
//! # fn hello(self, _: context::Context, name: String) -> Self::HelloFut {
|
||||
//! # future::ready(format!("Hello, {}!", name))
|
||||
//! # future::ready(format!("Hello, {name}!"))
|
||||
//! # }
|
||||
//! # }
|
||||
//! # #[cfg(not(feature = "tokio1"))]
|
||||
//! # fn main() {}
|
||||
//! # #[cfg(feature = "tokio1")]
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> io::Result<()> {
|
||||
//! async fn main() -> anyhow::Result<()> {
|
||||
//! let (client_transport, server_transport) = tarpc::transport::channel::unbounded();
|
||||
//!
|
||||
//! let server = server::BaseChannel::with_defaults(server_transport);
|
||||
@@ -192,7 +190,7 @@
|
||||
//! // specifies a deadline and trace information which can be helpful in debugging requests.
|
||||
//! let hello = client.hello(context::current(), "Stim".to_string()).await?;
|
||||
//!
|
||||
//! println!("{}", hello);
|
||||
//! println!("{hello}");
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
@@ -211,7 +209,7 @@
|
||||
pub use serde;
|
||||
|
||||
#[cfg(feature = "serde-transport")]
|
||||
pub use tokio_serde;
|
||||
pub use {tokio_serde, tokio_util};
|
||||
|
||||
#[cfg(feature = "serde-transport")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde-transport")))]
|
||||
@@ -266,7 +264,7 @@ pub use tarpc_plugins::service;
|
||||
/// #[tarpc::server]
|
||||
/// impl World for HelloServer {
|
||||
/// async fn hello(self, _: context::Context, name: String) -> String {
|
||||
/// format!("Hello, {}! You are connected from {:?}.", name, self.0)
|
||||
/// format!("Hello, {name}! You are connected from {:?}.", self.0)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
@@ -292,7 +290,7 @@ pub use tarpc_plugins::service;
|
||||
/// fn hello(self, _: context::Context, name: String) -> Pin<Box<dyn Future<Output = String>
|
||||
/// + Send>> {
|
||||
/// Box::pin(async move {
|
||||
/// format!("Hello, {}! You are connected from {:?}.", name, self.0)
|
||||
/// format!("Hello, {name}! You are connected from {:?}.", self.0)
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
@@ -302,6 +300,7 @@ pub use tarpc_plugins::service;
|
||||
/// `async`, meaning that this should not break existing code.
|
||||
pub use tarpc_plugins::server;
|
||||
|
||||
pub(crate) mod cancellations;
|
||||
pub mod client;
|
||||
pub mod context;
|
||||
pub mod server;
|
||||
@@ -312,6 +311,7 @@ pub use crate::transport::sealed::Transport;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use futures::task::*;
|
||||
use std::sync::Arc;
|
||||
use std::{error::Error, fmt::Display, io, time::SystemTime};
|
||||
|
||||
/// A message from a client to a server.
|
||||
@@ -364,8 +364,9 @@ pub struct Response<T> {
|
||||
pub message: Result<T, ServerError>,
|
||||
}
|
||||
|
||||
/// An error response from a server to a client.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
/// An error indicating the server aborted the request early, e.g., due to request throttling.
|
||||
#[derive(thiserror::Error, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[error("{kind:?}: {detail}")]
|
||||
#[non_exhaustive]
|
||||
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ServerError {
|
||||
@@ -380,13 +381,30 @@ pub struct ServerError {
|
||||
/// The type of error that occurred to fail the request.
|
||||
pub kind: io::ErrorKind,
|
||||
/// A message describing more detail about the error that occurred.
|
||||
pub detail: Option<String>,
|
||||
pub detail: String,
|
||||
}
|
||||
|
||||
impl From<ServerError> for io::Error {
|
||||
fn from(e: ServerError) -> io::Error {
|
||||
io::Error::new(e.kind, e.detail.unwrap_or_default())
|
||||
}
|
||||
/// Critical errors that result in a Channel disconnecting.
|
||||
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
|
||||
pub enum ChannelError<E>
|
||||
where
|
||||
E: Error + Send + Sync + 'static,
|
||||
{
|
||||
/// Could not read from the transport.
|
||||
#[error("could not read from the transport")]
|
||||
Read(#[source] Arc<E>),
|
||||
/// Could not ready the transport for writes.
|
||||
#[error("could not ready the transport for writes")]
|
||||
Ready(#[source] E),
|
||||
/// Could not write to the transport.
|
||||
#[error("could not write to the transport")]
|
||||
Write(#[source] E),
|
||||
/// Could not flush the transport.
|
||||
#[error("could not flush the transport")]
|
||||
Flush(#[source] E),
|
||||
/// Could not close the write end of the transport.
|
||||
#[error("could not close the write end of the transport")]
|
||||
Close(#[source] E),
|
||||
}
|
||||
|
||||
impl<T> Request<T> {
|
||||
|
||||
@@ -42,12 +42,10 @@ where
|
||||
type Item = io::Result<Item>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<io::Result<Item>>> {
|
||||
self.project().inner.poll_next(cx).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("while reading from transport: {}", e.into()),
|
||||
)
|
||||
})
|
||||
self.project()
|
||||
.inner
|
||||
.poll_next(cx)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,39 +61,31 @@ where
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
self.project().inner.poll_ready(cx).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("while readying write half of transport: {}", e.into()),
|
||||
)
|
||||
})
|
||||
self.project()
|
||||
.inner
|
||||
.poll_ready(cx)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: SinkItem) -> io::Result<()> {
|
||||
self.project().inner.start_send(item).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("while writing to transport: {}", e.into()),
|
||||
)
|
||||
})
|
||||
self.project()
|
||||
.inner
|
||||
.start_send(item)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
self.project().inner.poll_flush(cx).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("while flushing transport: {}", e.into()),
|
||||
)
|
||||
})
|
||||
self.project()
|
||||
.inner
|
||||
.poll_flush(cx)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
self.project().inner.poll_close(cx).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("while closing write half of transport: {}", e.into()),
|
||||
)
|
||||
})
|
||||
self.project()
|
||||
.inner
|
||||
.poll_close(cx)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,14 +129,6 @@ pub mod tcp {
|
||||
tokio_util::codec::length_delimited,
|
||||
};
|
||||
|
||||
mod private {
|
||||
use super::*;
|
||||
|
||||
pub trait Sealed {}
|
||||
|
||||
impl<Item, SinkItem, Codec> Sealed for Transport<TcpStream, Item, SinkItem, Codec> {}
|
||||
}
|
||||
|
||||
impl<Item, SinkItem, Codec> Transport<TcpStream, Item, SinkItem, Codec> {
|
||||
/// Returns the peer address of the underlying TcpStream.
|
||||
pub fn peer_addr(&self) -> io::Result<SocketAddr> {
|
||||
@@ -159,6 +141,7 @@ pub mod tcp {
|
||||
}
|
||||
|
||||
/// A connection Future that also exposes the length-delimited framing config.
|
||||
#[must_use]
|
||||
#[pin_project]
|
||||
pub struct Connect<T, Item, SinkItem, CodecFn> {
|
||||
#[pin]
|
||||
@@ -286,6 +269,270 @@ pub mod tcp {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "unix"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "unix"))))]
|
||||
/// Unix Domain Socket support for generic transport using Tokio.
|
||||
pub mod unix {
|
||||
use {
|
||||
super::*,
|
||||
futures::ready,
|
||||
std::{marker::PhantomData, path::Path},
|
||||
tokio::net::{unix::SocketAddr, UnixListener, UnixStream},
|
||||
tokio_util::codec::length_delimited,
|
||||
};
|
||||
|
||||
impl<Item, SinkItem, Codec> Transport<UnixStream, Item, SinkItem, Codec> {
|
||||
/// Returns the socket address of the remote half of the underlying [`UnixStream`].
|
||||
pub fn peer_addr(&self) -> io::Result<SocketAddr> {
|
||||
self.inner.get_ref().get_ref().peer_addr()
|
||||
}
|
||||
/// Returns the socket address of the local half of the underlying [`UnixStream`].
|
||||
pub fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||
self.inner.get_ref().get_ref().local_addr()
|
||||
}
|
||||
}
|
||||
|
||||
/// A connection Future that also exposes the length-delimited framing config.
|
||||
#[must_use]
|
||||
#[pin_project]
|
||||
pub struct Connect<T, Item, SinkItem, CodecFn> {
|
||||
#[pin]
|
||||
inner: T,
|
||||
codec_fn: CodecFn,
|
||||
config: length_delimited::Builder,
|
||||
ghost: PhantomData<(fn(SinkItem), fn() -> Item)>,
|
||||
}
|
||||
|
||||
impl<T, Item, SinkItem, Codec, CodecFn> Future for Connect<T, Item, SinkItem, CodecFn>
|
||||
where
|
||||
T: Future<Output = io::Result<UnixStream>>,
|
||||
Item: for<'de> Deserialize<'de>,
|
||||
SinkItem: Serialize,
|
||||
Codec: Serializer<SinkItem> + Deserializer<Item>,
|
||||
CodecFn: Fn() -> Codec,
|
||||
{
|
||||
type Output = io::Result<Transport<UnixStream, Item, SinkItem, Codec>>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
let io = ready!(self.as_mut().project().inner.poll(cx))?;
|
||||
Poll::Ready(Ok(new(self.config.new_framed(io), (self.codec_fn)())))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Item, SinkItem, CodecFn> Connect<T, Item, SinkItem, CodecFn> {
|
||||
/// Returns an immutable reference to the length-delimited codec's config.
|
||||
pub fn config(&self) -> &length_delimited::Builder {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the length-delimited codec's config.
|
||||
pub fn config_mut(&mut self) -> &mut length_delimited::Builder {
|
||||
&mut self.config
|
||||
}
|
||||
}
|
||||
|
||||
/// Connects to socket named by `path`, wrapping the connection in a Unix Domain Socket
|
||||
/// transport.
|
||||
pub fn connect<P, Item, SinkItem, Codec, CodecFn>(
|
||||
path: P,
|
||||
codec_fn: CodecFn,
|
||||
) -> Connect<impl Future<Output = io::Result<UnixStream>>, Item, SinkItem, CodecFn>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Item: for<'de> Deserialize<'de>,
|
||||
SinkItem: Serialize,
|
||||
Codec: Serializer<SinkItem> + Deserializer<Item>,
|
||||
CodecFn: Fn() -> Codec,
|
||||
{
|
||||
Connect {
|
||||
inner: UnixStream::connect(path),
|
||||
codec_fn,
|
||||
config: LengthDelimitedCodec::builder(),
|
||||
ghost: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Listens on the socket named by `path`, wrapping accepted connections in Unix Domain Socket
|
||||
/// transports.
|
||||
pub async fn listen<P, Item, SinkItem, Codec, CodecFn>(
|
||||
path: P,
|
||||
codec_fn: CodecFn,
|
||||
) -> io::Result<Incoming<Item, SinkItem, Codec, CodecFn>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Item: for<'de> Deserialize<'de>,
|
||||
Codec: Serializer<SinkItem> + Deserializer<Item>,
|
||||
CodecFn: Fn() -> Codec,
|
||||
{
|
||||
let listener = UnixListener::bind(path)?;
|
||||
let local_addr = listener.local_addr()?;
|
||||
Ok(Incoming {
|
||||
listener,
|
||||
codec_fn,
|
||||
local_addr,
|
||||
config: LengthDelimitedCodec::builder(),
|
||||
ghost: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// A [`UnixListener`] that wraps connections in [transports](Transport).
|
||||
#[pin_project]
|
||||
#[derive(Debug)]
|
||||
pub struct Incoming<Item, SinkItem, Codec, CodecFn> {
|
||||
listener: UnixListener,
|
||||
local_addr: SocketAddr,
|
||||
codec_fn: CodecFn,
|
||||
config: length_delimited::Builder,
|
||||
ghost: PhantomData<(fn() -> Item, fn(SinkItem), Codec)>,
|
||||
}
|
||||
|
||||
impl<Item, SinkItem, Codec, CodecFn> Incoming<Item, SinkItem, Codec, CodecFn> {
|
||||
/// Returns the the socket address being listened on.
|
||||
pub fn local_addr(&self) -> &SocketAddr {
|
||||
&self.local_addr
|
||||
}
|
||||
|
||||
/// Returns an immutable reference to the length-delimited codec's config.
|
||||
pub fn config(&self) -> &length_delimited::Builder {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the length-delimited codec's config.
|
||||
pub fn config_mut(&mut self) -> &mut length_delimited::Builder {
|
||||
&mut self.config
|
||||
}
|
||||
}
|
||||
|
||||
impl<Item, SinkItem, Codec, CodecFn> Stream for Incoming<Item, SinkItem, Codec, CodecFn>
|
||||
where
|
||||
Item: for<'de> Deserialize<'de>,
|
||||
SinkItem: Serialize,
|
||||
Codec: Serializer<SinkItem> + Deserializer<Item>,
|
||||
CodecFn: Fn() -> Codec,
|
||||
{
|
||||
type Item = io::Result<Transport<UnixStream, Item, SinkItem, Codec>>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let conn: UnixStream = ready!(self.as_mut().project().listener.poll_accept(cx)?).0;
|
||||
Poll::Ready(Some(Ok(new(
|
||||
self.config.new_framed(conn),
|
||||
(self.codec_fn)(),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
/// A temporary `PathBuf` that lives in `std::env::temp_dir` and is removed on drop.
|
||||
pub struct TempPathBuf(std::path::PathBuf);
|
||||
|
||||
impl TempPathBuf {
|
||||
/// A named socket that results in `<tempdir>/<name>`
|
||||
pub fn new<S: AsRef<str>>(name: S) -> Self {
|
||||
let mut sock = std::env::temp_dir();
|
||||
sock.push(name.as_ref());
|
||||
Self(sock)
|
||||
}
|
||||
|
||||
/// Appends a random hex string to the socket name resulting in
|
||||
/// `<tempdir>/<name>_<xxxxx>`
|
||||
pub fn with_random<S: AsRef<str>>(name: S) -> Self {
|
||||
Self::new(format!("{}_{:016x}", name.as_ref(), rand::random::<u64>()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<std::path::Path> for TempPathBuf {
|
||||
fn as_ref(&self) -> &std::path::Path {
|
||||
self.0.as_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TempPathBuf {
|
||||
fn drop(&mut self) {
|
||||
// This will remove the file pointed to by this PathBuf if it exists, however Err's can
|
||||
// be returned such as attempting to remove a non-existing file, or one which we don't
|
||||
// have permission to remove. In these cases the Err is swallowed
|
||||
let _ = std::fs::remove_file(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tokio_serde::formats::SymmetricalJson;
|
||||
|
||||
#[test]
|
||||
fn temp_path_buf_non_random() {
|
||||
let sock = TempPathBuf::new("test");
|
||||
let mut good = std::env::temp_dir();
|
||||
good.push("test");
|
||||
assert_eq!(sock.as_ref(), good);
|
||||
assert_eq!(sock.as_ref().file_name().unwrap(), "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn temp_path_buf_random() {
|
||||
let sock = TempPathBuf::with_random("test");
|
||||
let good = std::env::temp_dir();
|
||||
assert!(sock.as_ref().starts_with(good));
|
||||
// Since there are 16 random characters we just assert the file_name has the right name
|
||||
// and starts with the correct string 'test_'
|
||||
// file name: test_xxxxxxxxxxxxxxxx
|
||||
// test = 4
|
||||
// _ = 1
|
||||
// <hex> = 16
|
||||
// total = 21
|
||||
let fname = sock.as_ref().file_name().unwrap().to_string_lossy();
|
||||
assert!(fname.starts_with("test_"));
|
||||
assert_eq!(fname.len(), 21);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn temp_path_buf_non_existing() {
|
||||
let sock = TempPathBuf::with_random("test");
|
||||
let sock_path = std::path::PathBuf::from(sock.as_ref());
|
||||
|
||||
// No actual file has been created yet
|
||||
assert!(!sock_path.exists());
|
||||
// Should not panic
|
||||
std::mem::drop(sock);
|
||||
assert!(!sock_path.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn temp_path_buf_existing_file() {
|
||||
let sock = TempPathBuf::with_random("test");
|
||||
let sock_path = std::path::PathBuf::from(sock.as_ref());
|
||||
let _file = std::fs::File::create(&sock).unwrap();
|
||||
assert!(sock_path.exists());
|
||||
std::mem::drop(sock);
|
||||
assert!(!sock_path.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn temp_path_buf_preexisting_file() {
|
||||
let mut pre_existing = std::env::temp_dir();
|
||||
pre_existing.push("test");
|
||||
let _file = std::fs::File::create(&pre_existing).unwrap();
|
||||
let sock = TempPathBuf::new("test");
|
||||
let sock_path = std::path::PathBuf::from(sock.as_ref());
|
||||
assert!(sock_path.exists());
|
||||
std::mem::drop(sock);
|
||||
assert!(!sock_path.exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn temp_path_buf_for_socket() {
|
||||
let sock = TempPathBuf::with_random("test");
|
||||
// Save path for testing after drop
|
||||
let sock_path = std::path::PathBuf::from(sock.as_ref());
|
||||
// create the actual socket
|
||||
let _ = listen(&sock, SymmetricalJson::<String>::default).await;
|
||||
assert!(sock_path.exists());
|
||||
std::mem::drop(sock);
|
||||
assert!(!sock_path.exists());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Transport;
|
||||
@@ -300,7 +547,7 @@ mod tests {
|
||||
use tokio_serde::formats::SymmetricalJson;
|
||||
|
||||
fn ctx() -> Context<'static> {
|
||||
Context::from_waker(&noop_waker_ref())
|
||||
Context::from_waker(noop_waker_ref())
|
||||
}
|
||||
|
||||
struct TestIo(Cursor<Vec<u8>>);
|
||||
@@ -402,4 +649,24 @@ mod tests {
|
||||
assert_matches!(transport.next().await, None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "unix"))]
|
||||
#[tokio::test]
|
||||
async fn uds() -> io::Result<()> {
|
||||
use super::unix;
|
||||
use super::*;
|
||||
|
||||
let sock = unix::TempPathBuf::with_random("uds");
|
||||
let mut listener = unix::listen(&sock, SymmetricalJson::<String>::default).await?;
|
||||
tokio::spawn(async move {
|
||||
let mut transport = listener.next().await.unwrap().unwrap();
|
||||
let message = transport.next().await.unwrap().unwrap();
|
||||
transport.send(message).await.unwrap();
|
||||
});
|
||||
let mut transport = unix::connect(&sock, SymmetricalJson::<String>::default).await?;
|
||||
transport.send(String::from("test")).await?;
|
||||
assert_matches!(transport.next().await, Some(Ok(s)) if s == "test");
|
||||
assert_matches!(transport.next().await, None);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -94,11 +94,14 @@ impl InFlightRequests {
|
||||
}
|
||||
|
||||
/// Yields a request that has expired, aborting any ongoing processing of that request.
|
||||
pub fn poll_expired(
|
||||
&mut self,
|
||||
cx: &mut Context,
|
||||
) -> Poll<Option<Result<u64, tokio::time::error::Error>>> {
|
||||
self.deadlines.poll_expired(cx).map_ok(|expired| {
|
||||
pub fn poll_expired(&mut self, cx: &mut Context) -> Poll<Option<u64>> {
|
||||
if self.deadlines.is_empty() {
|
||||
// TODO(https://github.com/tokio-rs/tokio/issues/4161)
|
||||
// This is a workaround for DelayQueue not always treating this case correctly.
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
self.deadlines.poll_expired(cx).map(|expired| {
|
||||
let expired = expired?;
|
||||
if let Some(RequestData {
|
||||
abort_handle, span, ..
|
||||
}) = self.request_data.remove(expired.get_ref())
|
||||
@@ -108,7 +111,7 @@ impl InFlightRequests {
|
||||
abort_handle.abort();
|
||||
tracing::error!("DeadlineExceeded");
|
||||
}
|
||||
expired.into_inner()
|
||||
Some(expired.into_inner())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -156,7 +159,7 @@ mod tests {
|
||||
|
||||
assert_matches!(
|
||||
in_flight_requests.poll_expired(&mut noop_context()),
|
||||
Poll::Ready(Some(Ok(_)))
|
||||
Poll::Ready(Some(_))
|
||||
);
|
||||
assert_matches!(
|
||||
abortable_future.poll_unpin(&mut noop_context()),
|
||||
@@ -173,7 +176,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let mut abortable_future = Box::new(Abortable::new(pending::<()>(), abort_registration));
|
||||
|
||||
assert_eq!(in_flight_requests.cancel_request(0), true);
|
||||
assert!(in_flight_requests.cancel_request(0));
|
||||
assert_matches!(
|
||||
abortable_future.poll_unpin(&mut noop_context()),
|
||||
Poll::Ready(Err(_))
|
||||
@@ -184,12 +187,31 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn remove_request_doesnt_abort() {
|
||||
let mut in_flight_requests = InFlightRequests::default();
|
||||
assert!(in_flight_requests.deadlines.is_empty());
|
||||
|
||||
let abort_registration = in_flight_requests
|
||||
.start_request(0, SystemTime::now(), Span::current())
|
||||
.start_request(
|
||||
0,
|
||||
SystemTime::now() + std::time::Duration::from_secs(10),
|
||||
Span::current(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut abortable_future = Box::new(Abortable::new(pending::<()>(), abort_registration));
|
||||
|
||||
// Precondition: Pending expiration
|
||||
assert_matches!(
|
||||
in_flight_requests.poll_expired(&mut noop_context()),
|
||||
Poll::Pending
|
||||
);
|
||||
assert!(!in_flight_requests.deadlines.is_empty());
|
||||
|
||||
assert_matches!(in_flight_requests.remove_request(0), Some(_));
|
||||
// Postcondition: No pending expirations
|
||||
assert!(in_flight_requests.deadlines.is_empty());
|
||||
assert_matches!(
|
||||
in_flight_requests.poll_expired(&mut noop_context()),
|
||||
Poll::Ready(None)
|
||||
);
|
||||
assert_matches!(
|
||||
abortable_future.poll_unpin(&mut noop_context()),
|
||||
Poll::Pending
|
||||
|
||||
49
tarpc/src/server/incoming.rs
Normal file
49
tarpc/src/server/incoming.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use super::{
|
||||
limits::{channels_per_key::MaxChannelsPerKey, requests_per_channel::MaxRequestsPerChannel},
|
||||
Channel,
|
||||
};
|
||||
use futures::prelude::*;
|
||||
use std::{fmt, hash::Hash};
|
||||
|
||||
#[cfg(feature = "tokio1")]
|
||||
use super::{tokio::TokioServerExecutor, Serve};
|
||||
|
||||
/// An extension trait for [streams](futures::prelude::Stream) of [`Channels`](Channel).
|
||||
pub trait Incoming<C>
|
||||
where
|
||||
Self: Sized + Stream<Item = C>,
|
||||
C: Channel,
|
||||
{
|
||||
/// Enforces channel per-key limits.
|
||||
fn max_channels_per_key<K, KF>(self, n: u32, keymaker: KF) -> MaxChannelsPerKey<Self, K, KF>
|
||||
where
|
||||
K: fmt::Display + Eq + Hash + Clone + Unpin,
|
||||
KF: Fn(&C) -> K,
|
||||
{
|
||||
MaxChannelsPerKey::new(self, n, keymaker)
|
||||
}
|
||||
|
||||
/// Caps the number of concurrent requests per channel.
|
||||
fn max_concurrent_requests_per_channel(self, n: usize) -> MaxRequestsPerChannel<Self> {
|
||||
MaxRequestsPerChannel::new(self, n)
|
||||
}
|
||||
|
||||
/// [Executes](Channel::execute) each incoming channel. Each channel will be handled
|
||||
/// concurrently by spawning on tokio's default executor, and each request will be also
|
||||
/// be spawned on tokio's default executor.
|
||||
#[cfg(feature = "tokio1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tokio1")))]
|
||||
fn execute<S>(self, serve: S) -> TokioServerExecutor<Self, S>
|
||||
where
|
||||
S: Serve<C::Req, Resp = C::Resp>,
|
||||
{
|
||||
TokioServerExecutor::new(self, serve)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, C> Incoming<C> for S
|
||||
where
|
||||
S: Sized + Stream<Item = C>,
|
||||
C: Channel,
|
||||
{
|
||||
}
|
||||
5
tarpc/src/server/limits.rs
Normal file
5
tarpc/src/server/limits.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
/// Provides functionality to limit the number of active channels.
|
||||
pub mod channels_per_key;
|
||||
|
||||
/// Provides a [channel](crate::server::Channel) that limits the number of in-flight requests.
|
||||
pub mod requests_per_channel;
|
||||
@@ -9,20 +9,23 @@ use crate::{
|
||||
util::Compact,
|
||||
};
|
||||
use fnv::FnvHashMap;
|
||||
use futures::{future::AbortRegistration, prelude::*, ready, stream::Fuse, task::*};
|
||||
use futures::{prelude::*, ready, stream::Fuse, task::*};
|
||||
use pin_project::pin_project;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::{
|
||||
collections::hash_map::Entry, convert::TryFrom, fmt, hash::Hash, marker::Unpin, pin::Pin,
|
||||
time::SystemTime,
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
/// A single-threaded filter that drops channels based on per-key limits.
|
||||
/// An [`Incoming`](crate::server::incoming::Incoming) stream that drops new channels based on
|
||||
/// per-key limits.
|
||||
///
|
||||
/// The decision to drop a Channel is made once at the time the Channel materializes. Once a
|
||||
/// Channel is yielded, it will not be prematurely dropped.
|
||||
#[pin_project]
|
||||
#[derive(Debug)]
|
||||
pub struct ChannelFilter<S, K, F>
|
||||
pub struct MaxChannelsPerKey<S, K, F>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
{
|
||||
@@ -35,7 +38,7 @@ where
|
||||
keymaker: F,
|
||||
}
|
||||
|
||||
/// A channel that is tracked by a ChannelFilter.
|
||||
/// A channel that is tracked by [`MaxChannelsPerKey`].
|
||||
#[pin_project]
|
||||
#[derive(Debug)]
|
||||
pub struct TrackedChannel<C, K> {
|
||||
@@ -116,15 +119,6 @@ where
|
||||
fn transport(&self) -> &Self::Transport {
|
||||
self.inner.transport()
|
||||
}
|
||||
|
||||
fn start_request(
|
||||
mut self: Pin<&mut Self>,
|
||||
id: u64,
|
||||
deadline: SystemTime,
|
||||
span: tracing::Span,
|
||||
) -> Result<AbortRegistration, super::in_flight_requests::AlreadyExistsError> {
|
||||
self.inner_pin_mut().start_request(id, deadline, span)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, K> TrackedChannel<C, K> {
|
||||
@@ -139,7 +133,7 @@ impl<C, K> TrackedChannel<C, K> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, K, F> ChannelFilter<S, K, F>
|
||||
impl<S, K, F> MaxChannelsPerKey<S, K, F>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
S: Stream,
|
||||
@@ -148,7 +142,7 @@ where
|
||||
/// Sheds new channels to stay under configured limits.
|
||||
pub(crate) fn new(listener: S, channels_per_key: u32, keymaker: F) -> Self {
|
||||
let (dropped_keys_tx, dropped_keys) = mpsc::unbounded_channel();
|
||||
ChannelFilter {
|
||||
MaxChannelsPerKey {
|
||||
listener: listener.fuse(),
|
||||
channels_per_key,
|
||||
dropped_keys,
|
||||
@@ -159,7 +153,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, K, F> ChannelFilter<S, K, F>
|
||||
impl<S, K, F> MaxChannelsPerKey<S, K, F>
|
||||
where
|
||||
S: Stream,
|
||||
K: fmt::Display + Eq + Hash + Clone + Unpin,
|
||||
@@ -251,7 +245,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, K, F> Stream for ChannelFilter<S, K, F>
|
||||
impl<S, K, F> Stream for MaxChannelsPerKey<S, K, F>
|
||||
where
|
||||
S: Stream,
|
||||
K: fmt::Display + Eq + Hash + Clone + Unpin,
|
||||
@@ -288,7 +282,7 @@ where
|
||||
fn ctx() -> Context<'static> {
|
||||
use futures::task::*;
|
||||
|
||||
Context::from_waker(&noop_waker_ref())
|
||||
Context::from_waker(noop_waker_ref())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -354,7 +348,7 @@ fn channel_filter_increment_channels_for_key() {
|
||||
key: &'static str,
|
||||
}
|
||||
let (_, listener) = futures::channel::mpsc::unbounded();
|
||||
let filter = ChannelFilter::new(listener, 2, |chan: &TestChannel| chan.key);
|
||||
let filter = MaxChannelsPerKey::new(listener, 2, |chan: &TestChannel| chan.key);
|
||||
pin_mut!(filter);
|
||||
let tracker1 = filter.as_mut().increment_channels_for_key("key").unwrap();
|
||||
assert_eq!(Arc::strong_count(&tracker1), 1);
|
||||
@@ -375,7 +369,7 @@ fn channel_filter_handle_new_channel() {
|
||||
key: &'static str,
|
||||
}
|
||||
let (_, listener) = futures::channel::mpsc::unbounded();
|
||||
let filter = ChannelFilter::new(listener, 2, |chan: &TestChannel| chan.key);
|
||||
let filter = MaxChannelsPerKey::new(listener, 2, |chan: &TestChannel| chan.key);
|
||||
pin_mut!(filter);
|
||||
let channel1 = filter
|
||||
.as_mut()
|
||||
@@ -407,7 +401,7 @@ fn channel_filter_poll_listener() {
|
||||
key: &'static str,
|
||||
}
|
||||
let (new_channels, listener) = futures::channel::mpsc::unbounded();
|
||||
let filter = ChannelFilter::new(listener, 2, |chan: &TestChannel| chan.key);
|
||||
let filter = MaxChannelsPerKey::new(listener, 2, |chan: &TestChannel| chan.key);
|
||||
pin_mut!(filter);
|
||||
|
||||
new_channels
|
||||
@@ -443,7 +437,7 @@ fn channel_filter_poll_closed_channels() {
|
||||
key: &'static str,
|
||||
}
|
||||
let (new_channels, listener) = futures::channel::mpsc::unbounded();
|
||||
let filter = ChannelFilter::new(listener, 2, |chan: &TestChannel| chan.key);
|
||||
let filter = MaxChannelsPerKey::new(listener, 2, |chan: &TestChannel| chan.key);
|
||||
pin_mut!(filter);
|
||||
|
||||
new_channels
|
||||
@@ -471,7 +465,7 @@ fn channel_filter_stream() {
|
||||
key: &'static str,
|
||||
}
|
||||
let (new_channels, listener) = futures::channel::mpsc::unbounded();
|
||||
let filter = ChannelFilter::new(listener, 2, |chan: &TestChannel| chan.key);
|
||||
let filter = MaxChannelsPerKey::new(listener, 2, |chan: &TestChannel| chan.key);
|
||||
pin_mut!(filter);
|
||||
|
||||
new_channels
|
||||
@@ -4,45 +4,49 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use super::{Channel, Config};
|
||||
use crate::{Response, ServerError};
|
||||
use futures::{future::AbortRegistration, prelude::*, ready, task::*};
|
||||
use crate::{
|
||||
server::{Channel, Config},
|
||||
Response, ServerError,
|
||||
};
|
||||
use futures::{prelude::*, ready, task::*};
|
||||
use pin_project::pin_project;
|
||||
use std::{io, pin::Pin, time::SystemTime};
|
||||
use tracing::Span;
|
||||
use std::{io, pin::Pin};
|
||||
|
||||
/// A [`Channel`] that limits the number of concurrent
|
||||
/// requests by throttling.
|
||||
/// A [`Channel`] that limits the number of concurrent requests by throttling.
|
||||
///
|
||||
/// Note that this is a very basic throttling heuristic. It is easy to set a number that is too low
|
||||
/// for the resources available to the server. For production use cases, a more advanced throttler
|
||||
/// is likely needed.
|
||||
#[pin_project]
|
||||
#[derive(Debug)]
|
||||
pub struct Throttler<C> {
|
||||
pub struct MaxRequests<C> {
|
||||
max_in_flight_requests: usize,
|
||||
#[pin]
|
||||
inner: C,
|
||||
}
|
||||
|
||||
impl<C> Throttler<C> {
|
||||
impl<C> MaxRequests<C> {
|
||||
/// Returns the inner channel.
|
||||
pub fn get_ref(&self) -> &C {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Throttler<C>
|
||||
impl<C> MaxRequests<C>
|
||||
where
|
||||
C: Channel,
|
||||
{
|
||||
/// Returns a new `Throttler` that wraps the given channel and limits concurrent requests to
|
||||
/// Returns a new `MaxRequests` that wraps the given channel and limits concurrent requests to
|
||||
/// `max_in_flight_requests`.
|
||||
pub fn new(inner: C, max_in_flight_requests: usize) -> Self {
|
||||
Throttler {
|
||||
MaxRequests {
|
||||
max_in_flight_requests,
|
||||
inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Stream for Throttler<C>
|
||||
impl<C> Stream for MaxRequests<C>
|
||||
where
|
||||
C: Channel,
|
||||
{
|
||||
@@ -54,19 +58,18 @@ where
|
||||
ready!(self.as_mut().project().inner.poll_ready(cx)?);
|
||||
|
||||
match ready!(self.as_mut().project().inner.poll_next(cx)?) {
|
||||
Some(request) => {
|
||||
tracing::debug!(
|
||||
rpc.trace_id = %request.context.trace_id(),
|
||||
Some(r) => {
|
||||
let _entered = r.span.enter();
|
||||
tracing::info!(
|
||||
in_flight_requests = self.as_mut().in_flight_requests(),
|
||||
max_in_flight_requests = *self.as_mut().project().max_in_flight_requests,
|
||||
"At in-flight request limit",
|
||||
"ThrottleRequest",
|
||||
);
|
||||
|
||||
self.as_mut().start_send(Response {
|
||||
request_id: request.id,
|
||||
request_id: r.request.id,
|
||||
message: Err(ServerError {
|
||||
kind: io::ErrorKind::WouldBlock,
|
||||
detail: Some("Server throttled the request.".into()),
|
||||
detail: "server throttled the request.".into(),
|
||||
}),
|
||||
})?;
|
||||
}
|
||||
@@ -77,7 +80,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Sink<Response<<C as Channel>::Resp>> for Throttler<C>
|
||||
impl<C> Sink<Response<<C as Channel>::Resp>> for MaxRequests<C>
|
||||
where
|
||||
C: Channel,
|
||||
{
|
||||
@@ -103,13 +106,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> AsRef<C> for Throttler<C> {
|
||||
impl<C> AsRef<C> for MaxRequests<C> {
|
||||
fn as_ref(&self) -> &C {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Channel for Throttler<C>
|
||||
impl<C> Channel for MaxRequests<C>
|
||||
where
|
||||
C: Channel,
|
||||
{
|
||||
@@ -128,27 +131,19 @@ where
|
||||
fn transport(&self) -> &Self::Transport {
|
||||
self.inner.transport()
|
||||
}
|
||||
|
||||
fn start_request(
|
||||
self: Pin<&mut Self>,
|
||||
id: u64,
|
||||
deadline: SystemTime,
|
||||
span: Span,
|
||||
) -> Result<AbortRegistration, super::in_flight_requests::AlreadyExistsError> {
|
||||
self.project().inner.start_request(id, deadline, span)
|
||||
}
|
||||
}
|
||||
|
||||
/// A stream of throttling channels.
|
||||
/// An [`Incoming`](crate::server::incoming::Incoming) stream of channels that enforce limits on
|
||||
/// the number of in-flight requests.
|
||||
#[pin_project]
|
||||
#[derive(Debug)]
|
||||
pub struct ThrottlerStream<S> {
|
||||
pub struct MaxRequestsPerChannel<S> {
|
||||
#[pin]
|
||||
inner: S,
|
||||
max_in_flight_requests: usize,
|
||||
}
|
||||
|
||||
impl<S> ThrottlerStream<S>
|
||||
impl<S> MaxRequestsPerChannel<S>
|
||||
where
|
||||
S: Stream,
|
||||
<S as Stream>::Item: Channel,
|
||||
@@ -161,16 +156,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Stream for ThrottlerStream<S>
|
||||
impl<S> Stream for MaxRequestsPerChannel<S>
|
||||
where
|
||||
S: Stream,
|
||||
<S as Stream>::Item: Channel,
|
||||
{
|
||||
type Item = Throttler<<S as Stream>::Item>;
|
||||
type Item = MaxRequests<<S as Stream>::Item>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||
match ready!(self.as_mut().project().inner.poll_next(cx)) {
|
||||
Some(channel) => Poll::Ready(Some(Throttler::new(
|
||||
Some(channel) => Poll::Ready(Some(MaxRequests::new(
|
||||
channel,
|
||||
*self.project().max_in_flight_requests,
|
||||
))),
|
||||
@@ -183,19 +178,20 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::{
|
||||
server::{
|
||||
in_flight_requests::AlreadyExistsError,
|
||||
testing::{self, FakeChannel, PollExt},
|
||||
},
|
||||
Request,
|
||||
use crate::server::{
|
||||
testing::{self, FakeChannel, PollExt},
|
||||
TrackedRequest,
|
||||
};
|
||||
use pin_utils::pin_mut;
|
||||
use std::{marker::PhantomData, time::Duration};
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use tracing::Span;
|
||||
|
||||
#[tokio::test]
|
||||
async fn throttler_in_flight_requests() {
|
||||
let throttler = Throttler {
|
||||
let throttler = MaxRequests {
|
||||
max_in_flight_requests: 0,
|
||||
inner: FakeChannel::default::<isize, isize>(),
|
||||
};
|
||||
@@ -215,28 +211,9 @@ mod tests {
|
||||
assert_eq!(throttler.as_mut().in_flight_requests(), 5);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn throttler_start_request() {
|
||||
let throttler = Throttler {
|
||||
max_in_flight_requests: 0,
|
||||
inner: FakeChannel::default::<isize, isize>(),
|
||||
};
|
||||
|
||||
pin_mut!(throttler);
|
||||
throttler
|
||||
.as_mut()
|
||||
.start_request(
|
||||
1,
|
||||
SystemTime::now() + Duration::from_secs(1),
|
||||
Span::current(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(throttler.inner.in_flight_requests.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn throttler_poll_next_done() {
|
||||
let throttler = Throttler {
|
||||
let throttler = MaxRequests {
|
||||
max_in_flight_requests: 0,
|
||||
inner: FakeChannel::default::<isize, isize>(),
|
||||
};
|
||||
@@ -247,7 +224,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn throttler_poll_next_some() -> io::Result<()> {
|
||||
let throttler = Throttler {
|
||||
let throttler = MaxRequests {
|
||||
max_in_flight_requests: 1,
|
||||
inner: FakeChannel::default::<isize, isize>(),
|
||||
};
|
||||
@@ -259,7 +236,7 @@ mod tests {
|
||||
throttler
|
||||
.as_mut()
|
||||
.poll_next(&mut testing::cx())?
|
||||
.map(|r| r.map(|r| (r.id, r.message))),
|
||||
.map(|r| r.map(|r| (r.request.id, r.request.message))),
|
||||
Poll::Ready(Some((0, 1)))
|
||||
);
|
||||
Ok(())
|
||||
@@ -267,7 +244,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn throttler_poll_next_throttled() {
|
||||
let throttler = Throttler {
|
||||
let throttler = MaxRequests {
|
||||
max_in_flight_requests: 0,
|
||||
inner: FakeChannel::default::<isize, isize>(),
|
||||
};
|
||||
@@ -283,7 +260,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn throttler_poll_next_throttled_sink_not_ready() {
|
||||
let throttler = Throttler {
|
||||
let throttler = MaxRequests {
|
||||
max_in_flight_requests: 0,
|
||||
inner: PendingSink::default::<isize, isize>(),
|
||||
};
|
||||
@@ -294,7 +271,8 @@ mod tests {
|
||||
ghost: PhantomData<fn(Out) -> In>,
|
||||
}
|
||||
impl PendingSink<(), ()> {
|
||||
pub fn default<Req, Resp>() -> PendingSink<io::Result<Request<Req>>, Response<Resp>> {
|
||||
pub fn default<Req, Resp>(
|
||||
) -> PendingSink<io::Result<TrackedRequest<Req>>, Response<Resp>> {
|
||||
PendingSink { ghost: PhantomData }
|
||||
}
|
||||
}
|
||||
@@ -319,7 +297,7 @@ mod tests {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
impl<Req, Resp> Channel for PendingSink<io::Result<Request<Req>>, Response<Resp>> {
|
||||
impl<Req, Resp> Channel for PendingSink<io::Result<TrackedRequest<Req>>, Response<Resp>> {
|
||||
type Req = Req;
|
||||
type Resp = Resp;
|
||||
type Transport = ();
|
||||
@@ -332,20 +310,12 @@ mod tests {
|
||||
fn transport(&self) -> &() {
|
||||
&()
|
||||
}
|
||||
fn start_request(
|
||||
self: Pin<&mut Self>,
|
||||
_id: u64,
|
||||
_deadline: SystemTime,
|
||||
_span: tracing::Span,
|
||||
) -> Result<AbortRegistration, AlreadyExistsError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn throttler_start_send() {
|
||||
let throttler = Throttler {
|
||||
let throttler = MaxRequests {
|
||||
max_in_flight_requests: 0,
|
||||
inner: FakeChannel::default::<isize, isize>(),
|
||||
};
|
||||
@@ -5,11 +5,12 @@
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use crate::{
|
||||
cancellations::{cancellations, CanceledRequests, RequestCancellation},
|
||||
context,
|
||||
server::{Channel, Config},
|
||||
server::{Channel, Config, ResponseGuard, TrackedRequest},
|
||||
Request, Response,
|
||||
};
|
||||
use futures::{future::AbortRegistration, task::*, Sink, Stream};
|
||||
use futures::{task::*, Sink, Stream};
|
||||
use pin_project::pin_project;
|
||||
use std::{collections::VecDeque, io, pin::Pin, time::SystemTime};
|
||||
use tracing::Span;
|
||||
@@ -22,6 +23,8 @@ pub(crate) struct FakeChannel<In, Out> {
|
||||
pub sink: VecDeque<Out>,
|
||||
pub config: Config,
|
||||
pub in_flight_requests: super::in_flight_requests::InFlightRequests,
|
||||
pub request_cancellation: RequestCancellation,
|
||||
pub canceled_requests: CanceledRequests,
|
||||
}
|
||||
|
||||
impl<In, Out> Stream for FakeChannel<In, Out>
|
||||
@@ -62,7 +65,7 @@ impl<In, Resp> Sink<Response<Resp>> for FakeChannel<In, Response<Resp>> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp> Channel for FakeChannel<io::Result<Request<Req>>, Response<Resp>>
|
||||
impl<Req, Resp> Channel for FakeChannel<io::Result<TrackedRequest<Req>>, Response<Resp>>
|
||||
where
|
||||
Req: Unpin,
|
||||
{
|
||||
@@ -81,39 +84,42 @@ where
|
||||
fn transport(&self) -> &() {
|
||||
&()
|
||||
}
|
||||
|
||||
fn start_request(
|
||||
self: Pin<&mut Self>,
|
||||
id: u64,
|
||||
deadline: SystemTime,
|
||||
span: Span,
|
||||
) -> Result<AbortRegistration, super::in_flight_requests::AlreadyExistsError> {
|
||||
self.project()
|
||||
.in_flight_requests
|
||||
.start_request(id, deadline, span)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp> FakeChannel<io::Result<Request<Req>>, Response<Resp>> {
|
||||
impl<Req, Resp> FakeChannel<io::Result<TrackedRequest<Req>>, Response<Resp>> {
|
||||
pub fn push_req(&mut self, id: u64, message: Req) {
|
||||
self.stream.push_back(Ok(Request {
|
||||
context: context::Context {
|
||||
deadline: SystemTime::UNIX_EPOCH,
|
||||
trace_context: Default::default(),
|
||||
let (_, abort_registration) = futures::future::AbortHandle::new_pair();
|
||||
let (request_cancellation, _) = cancellations();
|
||||
self.stream.push_back(Ok(TrackedRequest {
|
||||
request: Request {
|
||||
context: context::Context {
|
||||
deadline: SystemTime::UNIX_EPOCH,
|
||||
trace_context: Default::default(),
|
||||
},
|
||||
id,
|
||||
message,
|
||||
},
|
||||
abort_registration,
|
||||
span: Span::none(),
|
||||
response_guard: ResponseGuard {
|
||||
request_cancellation,
|
||||
request_id: id,
|
||||
cancel: false,
|
||||
},
|
||||
id,
|
||||
message,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl FakeChannel<(), ()> {
|
||||
pub fn default<Req, Resp>() -> FakeChannel<io::Result<Request<Req>>, Response<Resp>> {
|
||||
pub fn default<Req, Resp>() -> FakeChannel<io::Result<TrackedRequest<Req>>, Response<Resp>> {
|
||||
let (request_cancellation, canceled_requests) = cancellations();
|
||||
FakeChannel {
|
||||
stream: Default::default(),
|
||||
sink: Default::default(),
|
||||
config: Default::default(),
|
||||
in_flight_requests: Default::default(),
|
||||
request_cancellation,
|
||||
canceled_requests,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,5 +135,5 @@ impl<T> PollExt for Poll<Option<T>> {
|
||||
}
|
||||
|
||||
pub fn cx() -> Context<'static> {
|
||||
Context::from_waker(&noop_waker_ref())
|
||||
Context::from_waker(noop_waker_ref())
|
||||
}
|
||||
|
||||
113
tarpc/src/server/tokio.rs
Normal file
113
tarpc/src/server/tokio.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use super::{Channel, Requests, Serve};
|
||||
use futures::{prelude::*, ready, task::*};
|
||||
use pin_project::pin_project;
|
||||
use std::pin::Pin;
|
||||
|
||||
/// A future that drives the server by [spawning](tokio::spawn) a [`TokioChannelExecutor`](TokioChannelExecutor)
|
||||
/// for each new channel. Returned by
|
||||
/// [`Incoming::execute`](crate::server::incoming::Incoming::execute).
|
||||
#[must_use]
|
||||
#[pin_project]
|
||||
#[derive(Debug)]
|
||||
pub struct TokioServerExecutor<T, S> {
|
||||
#[pin]
|
||||
inner: T,
|
||||
serve: S,
|
||||
}
|
||||
|
||||
impl<T, S> TokioServerExecutor<T, S> {
|
||||
pub(crate) fn new(inner: T, serve: S) -> Self {
|
||||
Self { inner, serve }
|
||||
}
|
||||
}
|
||||
|
||||
/// A future that drives the server by [spawning](tokio::spawn) each [response
|
||||
/// handler](super::InFlightRequest::execute) on tokio's default executor. Returned by
|
||||
/// [`Channel::execute`](crate::server::Channel::execute).
|
||||
#[must_use]
|
||||
#[pin_project]
|
||||
#[derive(Debug)]
|
||||
pub struct TokioChannelExecutor<T, S> {
|
||||
#[pin]
|
||||
inner: T,
|
||||
serve: S,
|
||||
}
|
||||
|
||||
impl<T, S> TokioServerExecutor<T, S> {
|
||||
fn inner_pin_mut<'a>(self: &'a mut Pin<&mut Self>) -> Pin<&'a mut T> {
|
||||
self.as_mut().project().inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> TokioChannelExecutor<T, S> {
|
||||
fn inner_pin_mut<'a>(self: &'a mut Pin<&mut Self>) -> Pin<&'a mut T> {
|
||||
self.as_mut().project().inner
|
||||
}
|
||||
}
|
||||
|
||||
// Send + 'static execution helper methods.
|
||||
|
||||
impl<C> Requests<C>
|
||||
where
|
||||
C: Channel,
|
||||
C::Req: Send + 'static,
|
||||
C::Resp: Send + 'static,
|
||||
{
|
||||
/// Executes all requests using the given service function. Requests are handled concurrently
|
||||
/// by [spawning](::tokio::spawn) each handler on tokio's default executor.
|
||||
pub fn execute<S>(self, serve: S) -> TokioChannelExecutor<Self, S>
|
||||
where
|
||||
S: Serve<C::Req, Resp = C::Resp> + Send + 'static,
|
||||
{
|
||||
TokioChannelExecutor { inner: self, serve }
|
||||
}
|
||||
}
|
||||
|
||||
impl<St, C, Se> Future for TokioServerExecutor<St, Se>
|
||||
where
|
||||
St: Sized + Stream<Item = C>,
|
||||
C: Channel + Send + 'static,
|
||||
C::Req: Send + 'static,
|
||||
C::Resp: Send + 'static,
|
||||
Se: Serve<C::Req, Resp = C::Resp> + Send + 'static + Clone,
|
||||
Se::Fut: Send,
|
||||
{
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||
while let Some(channel) = ready!(self.inner_pin_mut().poll_next(cx)) {
|
||||
tokio::spawn(channel.execute(self.serve.clone()));
|
||||
}
|
||||
tracing::info!("Server shutting down.");
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S> Future for TokioChannelExecutor<Requests<C>, S>
|
||||
where
|
||||
C: Channel + 'static,
|
||||
C::Req: Send + 'static,
|
||||
C::Resp: Send + 'static,
|
||||
S: Serve<C::Req, Resp = C::Resp> + Send + 'static + Clone,
|
||||
S::Fut: Send,
|
||||
{
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
while let Some(response_handler) = ready!(self.inner_pin_mut().poll_next(cx)) {
|
||||
match response_handler {
|
||||
Ok(resp) => {
|
||||
let server = self.serve.clone();
|
||||
tokio::spawn(async move {
|
||||
resp.execute(server).await;
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Requests stream errored out: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
@@ -71,9 +71,9 @@ pub struct SpanId(u64);
|
||||
#[repr(u8)]
|
||||
pub enum SamplingDecision {
|
||||
/// The associated span was sampled by its creating process. Child spans must also be sampled.
|
||||
Sampled = opentelemetry::trace::TRACE_FLAG_SAMPLED,
|
||||
Sampled,
|
||||
/// The associated span was not sampled by its creating process.
|
||||
Unsampled = opentelemetry::trace::TRACE_FLAG_NOT_SAMPLED,
|
||||
Unsampled,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
@@ -138,25 +138,25 @@ impl From<u64> for SpanId {
|
||||
|
||||
impl From<opentelemetry::trace::TraceId> for TraceId {
|
||||
fn from(trace_id: opentelemetry::trace::TraceId) -> Self {
|
||||
Self::from(trace_id.to_u128())
|
||||
Self::from(u128::from_be_bytes(trace_id.to_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TraceId> for opentelemetry::trace::TraceId {
|
||||
fn from(trace_id: TraceId) -> Self {
|
||||
Self::from_u128(trace_id.into())
|
||||
Self::from_bytes(u128::from(trace_id).to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<opentelemetry::trace::SpanId> for SpanId {
|
||||
fn from(span_id: opentelemetry::trace::SpanId) -> Self {
|
||||
Self::from(span_id.to_u64())
|
||||
Self::from(u64::from_be_bytes(span_id.to_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SpanId> for opentelemetry::trace::SpanId {
|
||||
fn from(span_id: SpanId) -> Self {
|
||||
Self::from_u64(span_id.0)
|
||||
Self::from_bytes(u64::from(span_id).to_be_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,8 +173,8 @@ impl TryFrom<&tracing::Span> for Context {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&dyn opentelemetry::trace::Span> for Context {
|
||||
fn from(span: &dyn opentelemetry::trace::Span) -> Self {
|
||||
impl From<opentelemetry::trace::SpanRef<'_>> for Context {
|
||||
fn from(span: opentelemetry::trace::SpanRef<'_>) -> Self {
|
||||
let otel_ctx = span.span_context();
|
||||
Self {
|
||||
trace_id: TraceId::from(otel_ctx.trace_id()),
|
||||
@@ -184,6 +184,15 @@ impl From<&dyn opentelemetry::trace::Span> for Context {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SamplingDecision> for opentelemetry::trace::TraceFlags {
|
||||
fn from(decision: SamplingDecision) -> Self {
|
||||
match decision {
|
||||
SamplingDecision::Sampled => opentelemetry::trace::TraceFlags::SAMPLED,
|
||||
SamplingDecision::Unsampled => opentelemetry::trace::TraceFlags::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&opentelemetry::trace::SpanContext> for SamplingDecision {
|
||||
fn from(context: &opentelemetry::trace::SpanContext) -> Self {
|
||||
if context.is_sampled() {
|
||||
|
||||
@@ -151,7 +151,7 @@ impl<Item, SinkItem> Sink<SinkItem> for Channel<Item, SinkItem> {
|
||||
mod tests {
|
||||
use crate::{
|
||||
client, context,
|
||||
server::{BaseChannel, Incoming},
|
||||
server::{incoming::Incoming, BaseChannel},
|
||||
transport::{
|
||||
self,
|
||||
channel::{Channel, UnboundedChannel},
|
||||
@@ -170,7 +170,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn integration() -> io::Result<()> {
|
||||
async fn integration() -> anyhow::Result<()> {
|
||||
let _ = tracing_subscriber::fmt::try_init();
|
||||
|
||||
let (client_channel, server_channel) = transport::channel::unbounded();
|
||||
@@ -181,7 +181,7 @@ mod tests {
|
||||
future::ready(request.parse::<u64>().map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("{:?} is not an int", request),
|
||||
format!("{request:?} is not an int"),
|
||||
)
|
||||
}))
|
||||
}),
|
||||
|
||||
@@ -38,11 +38,34 @@ where
|
||||
H: BuildHasher,
|
||||
{
|
||||
fn compact(&mut self, usage_ratio_threshold: f64) {
|
||||
if self.capacity() > 1000 {
|
||||
let usage_ratio = self.len() as f64 / self.capacity() as f64;
|
||||
if usage_ratio < usage_ratio_threshold {
|
||||
self.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
let usage_ratio_threshold = usage_ratio_threshold.clamp(f64::MIN_POSITIVE, 1.);
|
||||
let cap = f64::max(1000., self.len() as f64 / usage_ratio_threshold);
|
||||
self.shrink_to(cap as usize);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compact() {
|
||||
let mut map = HashMap::with_capacity(2048);
|
||||
assert_eq!(map.capacity(), 3584);
|
||||
|
||||
// Make usage ratio 25%
|
||||
for i in 0..896 {
|
||||
map.insert(format!("k{i}"), "v");
|
||||
}
|
||||
|
||||
map.compact(-1.0);
|
||||
assert_eq!(map.capacity(), 3584);
|
||||
|
||||
map.compact(0.25);
|
||||
assert_eq!(map.capacity(), 3584);
|
||||
|
||||
map.compact(0.50);
|
||||
assert_eq!(map.capacity(), 1792);
|
||||
|
||||
map.compact(1.0);
|
||||
assert_eq!(map.capacity(), 1792);
|
||||
|
||||
map.compact(2.0);
|
||||
assert_eq!(map.capacity(), 1792);
|
||||
}
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
fn ui() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/compile_fail/*.rs");
|
||||
#[cfg(feature = "tokio1")]
|
||||
t.compile_fail("tests/compile_fail/tokio/*.rs");
|
||||
#[cfg(all(feature = "serde-transport", feature = "tcp"))]
|
||||
t.compile_fail("tests/compile_fail/serde_transport/*.rs");
|
||||
}
|
||||
|
||||
15
tarpc/tests/compile_fail/must_use_request_dispatch.rs
Normal file
15
tarpc/tests/compile_fail/must_use_request_dispatch.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use tarpc::client;
|
||||
|
||||
#[tarpc::service]
|
||||
trait World {
|
||||
async fn hello(name: String) -> String;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (client_transport, _) = tarpc::transport::channel::unbounded();
|
||||
|
||||
#[deny(unused_must_use)]
|
||||
{
|
||||
WorldClient::new(client::Config::default(), client_transport).dispatch;
|
||||
}
|
||||
}
|
||||
11
tarpc/tests/compile_fail/must_use_request_dispatch.stderr
Normal file
11
tarpc/tests/compile_fail/must_use_request_dispatch.stderr
Normal file
@@ -0,0 +1,11 @@
|
||||
error: unused `RequestDispatch` that must be used
|
||||
--> tests/compile_fail/must_use_request_dispatch.rs:13:9
|
||||
|
|
||||
13 | WorldClient::new(client::Config::default(), client_transport).dispatch;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: the lint level is defined here
|
||||
--> tests/compile_fail/must_use_request_dispatch.rs:11:12
|
||||
|
|
||||
11 | #[deny(unused_must_use)]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
@@ -0,0 +1,9 @@
|
||||
use tarpc::serde_transport;
|
||||
use tokio_serde::formats::Json;
|
||||
|
||||
fn main() {
|
||||
#[deny(unused_must_use)]
|
||||
{
|
||||
serde_transport::tcp::connect::<_, (), (), _, _>("0.0.0.0:0", Json::default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
error: unused `tarpc::serde_transport::tcp::Connect` that must be used
|
||||
--> tests/compile_fail/serde_transport/must_use_tcp_connect.rs:7:9
|
||||
|
|
||||
7 | serde_transport::tcp::connect::<_, (), (), _, _>("0.0.0.0:0", Json::default);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: the lint level is defined here
|
||||
--> tests/compile_fail/serde_transport/must_use_tcp_connect.rs:5:12
|
||||
|
|
||||
5 | #[deny(unused_must_use)]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
@@ -7,8 +7,8 @@ struct HelloServer;
|
||||
|
||||
#[tarpc::server]
|
||||
impl World for HelloServer {
|
||||
fn hello(name: String) -> String {
|
||||
format!("Hello, {}!", name)
|
||||
fn hello(name: String) -> String {
|
||||
format!("Hello, {name}!", name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@ error: not all trait items implemented, missing: `HelloFut`
|
||||
error: hint: `#[tarpc::server]` only rewrites async fns, and `fn hello` is not async
|
||||
--> $DIR/tarpc_server_missing_async.rs:10:5
|
||||
|
|
||||
10 | fn hello(name: String) -> String {
|
||||
10 | fn hello(name: String) -> String {
|
||||
| ^^
|
||||
|
||||
29
tarpc/tests/compile_fail/tokio/must_use_channel_executor.rs
Normal file
29
tarpc/tests/compile_fail/tokio/must_use_channel_executor.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use tarpc::{
|
||||
context,
|
||||
server::{self, Channel},
|
||||
};
|
||||
|
||||
#[tarpc::service]
|
||||
trait World {
|
||||
async fn hello(name: String) -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
#[tarpc::server]
|
||||
impl World for HelloServer {
|
||||
async fn hello(self, _: context::Context, name: String) -> String {
|
||||
format!("Hello, {name}!")
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (_, server_transport) = tarpc::transport::channel::unbounded();
|
||||
let server = server::BaseChannel::with_defaults(server_transport);
|
||||
|
||||
#[deny(unused_must_use)]
|
||||
{
|
||||
server.execute(HelloServer.serve());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
error: unused `TokioChannelExecutor` that must be used
|
||||
--> tests/compile_fail/tokio/must_use_channel_executor.rs:27:9
|
||||
|
|
||||
27 | server.execute(HelloServer.serve());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: the lint level is defined here
|
||||
--> tests/compile_fail/tokio/must_use_channel_executor.rs:25:12
|
||||
|
|
||||
25 | #[deny(unused_must_use)]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
30
tarpc/tests/compile_fail/tokio/must_use_server_executor.rs
Normal file
30
tarpc/tests/compile_fail/tokio/must_use_server_executor.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use futures::stream::once;
|
||||
use tarpc::{
|
||||
context,
|
||||
server::{self, incoming::Incoming},
|
||||
};
|
||||
|
||||
#[tarpc::service]
|
||||
trait World {
|
||||
async fn hello(name: String) -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
#[tarpc::server]
|
||||
impl World for HelloServer {
|
||||
async fn hello(self, _: context::Context, name: String) -> String {
|
||||
format!("Hello, {name}!")
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (_, server_transport) = tarpc::transport::channel::unbounded();
|
||||
let server = once(async move { server::BaseChannel::with_defaults(server_transport) });
|
||||
|
||||
#[deny(unused_must_use)]
|
||||
{
|
||||
server.execute(HelloServer.serve());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
error: unused `TokioServerExecutor` that must be used
|
||||
--> tests/compile_fail/tokio/must_use_server_executor.rs:28:9
|
||||
|
|
||||
28 | server.execute(HelloServer.serve());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: the lint level is defined here
|
||||
--> tests/compile_fail/tokio/must_use_server_executor.rs:26:12
|
||||
|
|
||||
26 | #[deny(unused_must_use)]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
@@ -1,14 +1,13 @@
|
||||
use futures::prelude::*;
|
||||
use std::io;
|
||||
use tarpc::serde_transport;
|
||||
use tarpc::{
|
||||
client, context,
|
||||
server::{BaseChannel, Incoming},
|
||||
server::{incoming::Incoming, BaseChannel},
|
||||
};
|
||||
use tokio_serde::formats::Json;
|
||||
|
||||
#[tarpc::derive_serde]
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum TestData {
|
||||
Black,
|
||||
White,
|
||||
@@ -33,7 +32,7 @@ impl ColorProtocol for ColorServer {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_call() -> io::Result<()> {
|
||||
async fn test_call() -> anyhow::Result<()> {
|
||||
let transport = tarpc::serde_transport::tcp::listen("localhost:56797", Json::default).await?;
|
||||
let addr = transport.local_addr();
|
||||
tokio::spawn(
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::time::{Duration, SystemTime};
|
||||
use tarpc::{
|
||||
client::{self},
|
||||
context,
|
||||
server::{self, BaseChannel, Channel, Incoming},
|
||||
server::{self, incoming::Incoming, BaseChannel, Channel},
|
||||
transport::channel,
|
||||
};
|
||||
use tokio::join;
|
||||
@@ -31,7 +31,7 @@ impl Service for Server {
|
||||
type HeyFut = Ready<String>;
|
||||
|
||||
fn hey(self, _: context::Context, name: String) -> Self::HeyFut {
|
||||
ready(format!("Hey, {}.", name))
|
||||
ready(format!("Hey, {name}."))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ async fn dropped_channel_aborts_in_flight_requests() -> anyhow::Result<()> {
|
||||
|
||||
#[cfg(all(feature = "serde-transport", feature = "tcp"))]
|
||||
#[tokio::test]
|
||||
async fn serde() -> anyhow::Result<()> {
|
||||
async fn serde_tcp() -> anyhow::Result<()> {
|
||||
use tarpc::serde_transport;
|
||||
use tokio_serde::formats::Json;
|
||||
|
||||
@@ -136,6 +136,37 @@ async fn serde() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "serde-transport", feature = "unix", unix))]
|
||||
#[tokio::test]
|
||||
async fn serde_uds() -> anyhow::Result<()> {
|
||||
use tarpc::serde_transport;
|
||||
use tokio_serde::formats::Json;
|
||||
|
||||
let _ = tracing_subscriber::fmt::try_init();
|
||||
|
||||
let sock = tarpc::serde_transport::unix::TempPathBuf::with_random("uds");
|
||||
let transport = tarpc::serde_transport::unix::listen(&sock, Json::default).await?;
|
||||
tokio::spawn(
|
||||
transport
|
||||
.take(1)
|
||||
.filter_map(|r| async { r.ok() })
|
||||
.map(BaseChannel::with_defaults)
|
||||
.execute(Server.serve()),
|
||||
);
|
||||
|
||||
let transport = serde_transport::unix::connect(&sock, Json::default).await?;
|
||||
let client = ServiceClient::new(client::Config::default(), transport).spawn();
|
||||
|
||||
// Save results using socket so we can clean the socket even if our test assertions fail
|
||||
let res1 = client.add(context::current(), 1, 2).await;
|
||||
let res2 = client.hey(context::current(), "Tim".to_string()).await;
|
||||
|
||||
assert_matches!(res1, Ok(3));
|
||||
assert_matches!(res2, Ok(ref s) if s == "Hey, Tim.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn concurrent() -> anyhow::Result<()> {
|
||||
let _ = tracing_subscriber::fmt::try_init();
|
||||
|
||||
Reference in New Issue
Block a user