mirror of
https://github.com/OMGeeky/tarpc.git
synced 2026-02-23 15:49:54 +01:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00751d2518 | ||
|
|
4394a52b65 | ||
|
|
70938501d7 | ||
|
|
d5f5cf4300 | ||
|
|
e2c4164d8c | ||
|
|
78124ef7a8 | ||
|
|
096d354b7e | ||
|
|
7ad0e4b070 | ||
|
|
64755d5329 | ||
|
|
3071422132 | ||
|
|
8847330dbe | ||
|
|
6d396520f4 | ||
|
|
79a2f7fe2f | ||
|
|
af66841f68 | ||
|
|
1ab4cfdff9 | ||
|
|
f7e03eeeb7 | ||
|
|
29067b7773 | ||
|
|
905e5be8bb | ||
|
|
5e4b97e589 | ||
|
|
9bd66b7e49 | ||
|
|
0ecc7a80c1 |
31
.travis.yml
31
.travis.yml
@@ -1,34 +1,13 @@
|
||||
language: rust
|
||||
sudo: false
|
||||
|
||||
rust:
|
||||
- nightly
|
||||
sudo: false
|
||||
cache: cargo
|
||||
|
||||
os:
|
||||
- osx
|
||||
- linux
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libcurl4-openssl-dev
|
||||
- libelf-dev
|
||||
- libdw-dev
|
||||
|
||||
before_script:
|
||||
- |
|
||||
pip install 'travis-cargo<0.2' --user &&
|
||||
export PATH=$HOME/.local/bin:$PATH
|
||||
|
||||
script:
|
||||
- |
|
||||
travis-cargo build -- --features tls && travis-cargo test -- --features tls && travis-cargo bench -- --features tls &&
|
||||
rustdoc --test README.md -L target/debug/deps -L target/debug &&
|
||||
travis-cargo build && travis-cargo test && travis-cargo bench
|
||||
|
||||
after_success:
|
||||
- travis-cargo coveralls --no-sudo
|
||||
|
||||
env:
|
||||
global:
|
||||
# override the default `--features unstable` used for the nightly branch
|
||||
- TRAVIS_CARGO_NIGHTLY_FEATURE=""
|
||||
- cargo test --all-targets --all-features
|
||||
- cargo test --doc --all-features
|
||||
|
||||
64
Cargo.toml
64
Cargo.toml
@@ -1,56 +1,10 @@
|
||||
[package]
|
||||
name = "tarpc"
|
||||
version = "0.12.0"
|
||||
authors = ["Adam Wright <adam.austin.wright@gmail.com>", "Tim Kuehn <timothy.j.kuehn@gmail.com>"]
|
||||
license = "MIT"
|
||||
documentation = "https://docs.rs/tarpc"
|
||||
homepage = "https://github.com/google/tarpc"
|
||||
repository = "https://github.com/google/tarpc"
|
||||
keywords = ["rpc", "network", "server", "api", "tls"]
|
||||
categories = ["asynchronous", "network-programming"]
|
||||
readme = "README.md"
|
||||
description = "An RPC framework for Rust with a focus on ease of use."
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "google/tarpc" }
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.0"
|
||||
byteorder = "1.0"
|
||||
bytes = "0.4"
|
||||
cfg-if = "0.1.0"
|
||||
futures = "0.1.11"
|
||||
lazy_static = "1.0"
|
||||
log = "0.4"
|
||||
net2 = "0.2"
|
||||
num_cpus = "1.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
tarpc-plugins = { path = "src/plugins", version = "0.4.0" }
|
||||
thread-pool = "0.1.1"
|
||||
tokio-codec = "0.1"
|
||||
tokio-core = "0.1.6"
|
||||
tokio-io = "0.1"
|
||||
tokio-proto = "0.1.1"
|
||||
tokio-service = "0.1"
|
||||
|
||||
# Optional dependencies
|
||||
native-tls = { version = "0.1", optional = true }
|
||||
tokio-tls = { version = "0.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
chrono = "0.4"
|
||||
env_logger = "0.5"
|
||||
futures-cpupool = "0.1"
|
||||
clap = "2.0"
|
||||
serde_bytes = "0.10"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dev-dependencies]
|
||||
security-framework = "0.2"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
tls = ["tokio-tls", "native-tls"]
|
||||
unstable = ["serde/unstable"]
|
||||
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"example-service",
|
||||
"rpc",
|
||||
"trace",
|
||||
"bincode-transport",
|
||||
"tarpc",
|
||||
"plugins",
|
||||
]
|
||||
|
||||
300
README.md
300
README.md
@@ -31,281 +31,107 @@ works with the community-backed library serde: any serde-serializable type can b
|
||||
arguments to tarpc fns.
|
||||
|
||||
## Usage
|
||||
**NB**: *this example is for master. Are you looking for other
|
||||
[versions](https://docs.rs/tarpc)?*
|
||||
|
||||
Add to your `Cargo.toml` dependencies:
|
||||
|
||||
```toml
|
||||
tarpc = "0.12.0"
|
||||
tarpc-plugins = "0.4.0"
|
||||
```
|
||||
|
||||
## Example: Sync
|
||||
|
||||
tarpc has two APIs: `sync` for blocking code and `future` for asynchronous
|
||||
code. Here's how to use the sync api.
|
||||
|
||||
```rust
|
||||
#![feature(plugin, use_extern_macros, proc_macro_path_invoc)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tarpc::sync::{client, server};
|
||||
use tarpc::sync::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
|
||||
service! {
|
||||
rpc hello(name: String) -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl SyncService for HelloServer {
|
||||
fn hello(&self, name: String) -> Result<String, Never> {
|
||||
Ok(format!("Hello, {}!", name))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let mut handle = HelloServer.listen("localhost:0", server::Options::default())
|
||||
.unwrap();
|
||||
tx.send(handle.addr()).unwrap();
|
||||
handle.run();
|
||||
});
|
||||
let client = SyncClient::connect(rx.recv().unwrap(), client::Options::default()).unwrap();
|
||||
println!("{}", client.hello("Mom".to_string()).unwrap());
|
||||
}
|
||||
tarpc = "0.14.0"
|
||||
```
|
||||
|
||||
The `service!` macro expands to a collection of items that form an
|
||||
rpc service. In the above example, the macro is called within the
|
||||
`hello_service` module. This module will contain `SyncClient`, `AsyncClient`,
|
||||
and `FutureClient` types, and `SyncService` and `AsyncService` traits. There is
|
||||
also a `ServiceExt` trait that provides starter `fn`s for services, with an
|
||||
umbrella impl for all services. These generated types make it easy and
|
||||
ergonomic to write servers without dealing with sockets or serialization
|
||||
`hello_service` module. This module will contain a `Client` stub and `Service` trait. There is
|
||||
These generated types make it easy and ergonomic to write servers without dealing with serialization
|
||||
directly. Simply implement one of the generated traits, and you're off to the
|
||||
races! See the `tarpc_examples` package for more examples.
|
||||
races!
|
||||
|
||||
## Example: Futures
|
||||
## Example
|
||||
|
||||
Here's the same service, implemented using futures.
|
||||
Here's a small service.
|
||||
|
||||
```rust
|
||||
#![feature(plugin, use_extern_macros, proc_macro_path_invoc)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
#![feature(futures_api, pin, arbitrary_self_types, await_macro, async_await, proc_macro_hygiene)]
|
||||
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate tokio_core;
|
||||
|
||||
use futures::Future;
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
use tokio_core::reactor;
|
||||
use futures::{
|
||||
compat::TokioDefaultSpawner,
|
||||
future::{self, Ready},
|
||||
prelude::*,
|
||||
};
|
||||
use tarpc::{
|
||||
client, context,
|
||||
server::{self, Handler},
|
||||
};
|
||||
use std::io;
|
||||
|
||||
service! {
|
||||
// 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! {
|
||||
/// Returns a greeting for name.
|
||||
rpc hello(name: String) -> String;
|
||||
}
|
||||
|
||||
// This is the type that implements the generated Service trait. It is the business logic
|
||||
// and is used to start the server.
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl FutureService for HelloServer {
|
||||
type HelloFut = Result<String, Never>;
|
||||
impl Service for HelloServer {
|
||||
// Each defined rpc generates two items in the trait, a fn that serves the RPC, and
|
||||
// an associated type representing the future output by the fn.
|
||||
|
||||
fn hello(&self, name: String) -> Self::HelloFut {
|
||||
Ok(format!("Hello, {}!", name))
|
||||
type HelloFut = Ready<String>;
|
||||
|
||||
fn hello(self, _: context::Context, name: String) -> Self::HelloFut {
|
||||
future::ready(format!("Hello, {}!", name))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (handle, server) = HelloServer.listen("localhost:10000".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default())
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
let options = client::Options::default().handle(reactor.handle());
|
||||
reactor.run(FutureClient::connect(handle.addr(), options)
|
||||
.map_err(tarpc::Error::from)
|
||||
.and_then(|client| client.hello("Mom".to_string()))
|
||||
.map(|resp| println!("{}", resp)))
|
||||
.unwrap();
|
||||
}
|
||||
```
|
||||
async fn run() -> io::Result<()> {
|
||||
// bincode_transport is provided by the associated crate bincode-transport. It makes it easy
|
||||
// to start up a serde-powered bincode serialization strategy over TCP.
|
||||
let transport = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||
let addr = transport.local_addr();
|
||||
|
||||
## Example: Futures + TLS
|
||||
// The server is configured with the defaults.
|
||||
let server = server::new(server::Config::default())
|
||||
// Server can listen on any type that implements the Transport trait.
|
||||
.incoming(transport)
|
||||
// Close the stream after the client connects
|
||||
.take(1)
|
||||
// serve is generated by the service! macro. It takes as input any type implementing
|
||||
// the generated Service trait.
|
||||
.respond_with(serve(HelloServer));
|
||||
|
||||
By default, tarpc internally uses a [`TcpStream`] for communication between your clients and
|
||||
servers. However, TCP by itself has no encryption. As a result, your communication will be sent in
|
||||
the clear. If you want your RPC communications to be encrypted, you can choose to use [TLS]. TLS
|
||||
operates as an encryption layer on top of TCP. When using TLS, your communication will occur over a
|
||||
[`TlsStream<TcpStream>`]. You can add the ability to make TLS clients and servers by adding `tarpc`
|
||||
with the `tls` feature flag enabled.
|
||||
tokio_executor::spawn(server.unit_error().boxed().compat());
|
||||
|
||||
When using TLS, some additional information is required. You will need to make [`TlsAcceptor`] and
|
||||
`client::tls::Context` structs; `client::tls::Context` requires a [`TlsConnector`]. The
|
||||
[`TlsAcceptor`] and [`TlsConnector`] types are defined in the [native-tls]. tarpc re-exports
|
||||
external TLS-related types in its `native_tls` module (`tarpc::native_tls`).
|
||||
let transport = await!(bincode_transport::connect(&addr))?;
|
||||
|
||||
[TLS]: https://en.wikipedia.org/wiki/Transport_Layer_Security
|
||||
[`TcpStream`]: https://docs.rs/tokio-core/0.1/tokio_core/net/struct.TcpStream.html
|
||||
[`TlsStream<TcpStream>`]: https://docs.rs/native-tls/0.1/native_tls/struct.TlsStream.html
|
||||
[`TlsAcceptor`]: https://docs.rs/native-tls/0.1/native_tls/struct.TlsAcceptor.html
|
||||
[`TlsConnector`]: https://docs.rs/native-tls/0.1/native_tls/struct.TlsConnector.html
|
||||
[native-tls]: https://github.com/sfackler/rust-native-tls
|
||||
// new_stub is generated by the service! macro. Like Server, it takes a config and any
|
||||
// Transport as input, and returns a Client, also generated by the macro.
|
||||
// by the service mcro.
|
||||
let mut client = await!(new_stub(client::Config::default(), transport))?;
|
||||
|
||||
Both TLS streams and TCP streams are supported in the same binary when the `tls` feature is enabled.
|
||||
However, if you are working with both stream types, ensure that you use the TLS clients with TLS
|
||||
servers and TCP clients with TCP servers.
|
||||
// The client has an RPC method for each RPC defined in service!. It takes the same args
|
||||
// as defined, with the addition of a Context, which is always the first arg. The Context
|
||||
// specifies a deadline and trace information which can be helpful in debugging requests.
|
||||
let hello = await!(client.hello(context::current(), "Stim".to_string()))?;
|
||||
|
||||
```rust,no_run
|
||||
#![feature(plugin, use_extern_macros, proc_macro_path_invoc)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
println!("{}", hello);
|
||||
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate tokio_core;
|
||||
|
||||
use futures::Future;
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt;
|
||||
use tarpc::tls;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
use tokio_core::reactor;
|
||||
use tarpc::native_tls::{Pkcs12, TlsAcceptor};
|
||||
|
||||
service! {
|
||||
rpc hello(name: String) -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl FutureService for HelloServer {
|
||||
type HelloFut = Result<String, Never>;
|
||||
|
||||
fn hello(&self, name: String) -> Self::HelloFut {
|
||||
Ok(format!("Hello, {}!", name))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_acceptor() -> TlsAcceptor {
|
||||
let buf = include_bytes!("test/identity.p12");
|
||||
let pkcs12 = Pkcs12::from_der(buf, "password").unwrap();
|
||||
TlsAcceptor::builder(pkcs12).unwrap().build().unwrap()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let acceptor = get_acceptor();
|
||||
let (handle, server) = HelloServer.listen("localhost:10000".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default().tls(acceptor)).unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
let options = client::Options::default()
|
||||
.handle(reactor.handle())
|
||||
.tls(tls::client::Context::new("foobar.com").unwrap());
|
||||
reactor.run(FutureClient::connect(handle.addr(), options)
|
||||
.map_err(tarpc::Error::from)
|
||||
.and_then(|client| client.hello("Mom".to_string()))
|
||||
.map(|resp| println!("{}", resp)))
|
||||
.unwrap();
|
||||
tarpc::init(TokioDefaultSpawner);
|
||||
tokio::run(run()
|
||||
.map_err(|e| eprintln!("Oh no: {}", e))
|
||||
.boxed()
|
||||
.compat(),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
### Sync vs Futures
|
||||
|
||||
A single `service!` invocation generates code for both synchronous and future-based applications.
|
||||
It's up to the user whether they want to implement the sync API or the futures API. The sync API has
|
||||
the simplest programming model, at the cost of some overhead - each RPC is handled in its own
|
||||
thread. The futures API is based on tokio and can run on any tokio-compatible executor. This mean a
|
||||
service that implements the futures API for a tarpc service can run on a single thread, avoiding
|
||||
context switches and the memory overhead of having a thread per RPC.
|
||||
|
||||
### Errors
|
||||
|
||||
All generated tarpc RPC methods return either `tarpc::Result<T, E>` or something like `Future<T,
|
||||
E>`. The error type defaults to `tarpc::util::Never` (a wrapper for `!` which implements
|
||||
`std::error::Error`) if no error type is explicitly specified in the `service!` macro invocation. An
|
||||
error type can be specified like so:
|
||||
|
||||
```rust,ignore
|
||||
use tarpc::util::Message;
|
||||
|
||||
service! {
|
||||
rpc hello(name: String) -> String | Message
|
||||
}
|
||||
```
|
||||
|
||||
`tarpc::util::Message` is just a wrapper around string that implements `std::error::Error` provided
|
||||
for service implementations that don't require complex error handling. The pipe is used as syntax
|
||||
for specifying the error type in a way that's agnostic of whether the service implementation is
|
||||
synchronous or future-based. Note that in the simpler examples in the readme, no pipe is used, and
|
||||
the macro automatically chooses `tarpc::util::Never` as the error type.
|
||||
|
||||
The above declaration would produce the following synchronous service trait:
|
||||
|
||||
```rust,ignore
|
||||
trait SyncService {
|
||||
fn hello(&self, name: String) -> Result<String, Message>;
|
||||
}
|
||||
```
|
||||
|
||||
and the following future-based trait:
|
||||
|
||||
```rust,ignore
|
||||
trait FutureService {
|
||||
type HelloFut: IntoFuture<String, Message>;
|
||||
|
||||
fn hello(&mut self, name: String) -> Self::HelloFut;
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
## Service Documentation
|
||||
|
||||
Use `cargo doc` as you normally would to see the documentation created for all
|
||||
items expanded by a `service!` invocation.
|
||||
|
||||
## Additional Features
|
||||
|
||||
- Concurrent requests from a single client.
|
||||
- Compatible with tokio services.
|
||||
- Run any number of clients and services on a single event loop.
|
||||
- Any type that `impl`s `serde`'s `Serialize` and `Deserialize` can be used in
|
||||
rpc signatures.
|
||||
- Attributes can be specified on rpc methods. These will be included on both the
|
||||
services' trait methods as well as on the clients' stub methods.
|
||||
|
||||
## Gaps/Potential Improvements (not necessarily actively being worked on)
|
||||
|
||||
- Configurable server rate limiting.
|
||||
- Automatic client retries with exponential backoff when server is busy.
|
||||
- Load balancing
|
||||
- Service discovery
|
||||
- Automatically reconnect on the client side when the connection cuts out.
|
||||
- Support generic serialization protocols.
|
||||
|
||||
## Contributing
|
||||
|
||||
To contribute to tarpc, please see [CONTRIBUTING](CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
tarpc is distributed under the terms of the MIT license.
|
||||
|
||||
See [LICENSE](LICENSE) for details.
|
||||
|
||||
43
RELEASES.md
43
RELEASES.md
@@ -1,21 +1,52 @@
|
||||
## 0.13.0 (2018-10-16)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
Version 0.13 marks a significant departure from previous versions of tarpc. The
|
||||
API has changed significantly. The tokio-proto crate has been torn out and
|
||||
replaced with a homegrown rpc framework. Additionally, the crate has been
|
||||
modularized, so that the tarpc crate itself contains only the macro code.
|
||||
|
||||
### New Crates
|
||||
|
||||
- crate rpc contains the core client/server request-response framework, as well as a transport trait.
|
||||
- crate bincode-transport implements a transport that works almost exactly as tarpc works today (not to say it's wire-compatible).
|
||||
- crate trace has some foundational types for tracing. This isn't really fleshed out yet, but it's useful for in-process log tracing, at least.
|
||||
|
||||
All crates are now at the top level. e.g. tarpc-plugins is now tarpc/plugins rather than tarpc/src/plugins. tarpc itself is now a *very* small code surface, as most functionality has been moved into the other more granular crates.
|
||||
|
||||
### New Features
|
||||
- deadlines: all requests specify a deadline, and a server will stop processing a response when past its deadline.
|
||||
- client cancellation propagation: when a client drops a request, the client sends a message to the server informing it to cancel its response. This means cancellations can propagate across multiple server hops.
|
||||
- trace context stuff as mentioned above
|
||||
- more server configuration for total connection limits, per-connection request limits, etc.
|
||||
|
||||
### Removals
|
||||
- no more shutdown handle. I left it out for now because of time and not being sure what the right solution is.
|
||||
- all async now, no blocking stub or server interface. This helps with maintainability, and async/await makes async code much more usable. The service trait is thusly renamed Service, and the client is renamed Client.
|
||||
- no built-in transport. Tarpc is now transport agnostic (see bincode-transport for transitioning existing uses).
|
||||
- going along with the previous bullet, no preferred transport means no TLS support at this time. We could make a tls transport or make bincode-transport compatible with TLS.
|
||||
- a lot of examples were removed because I couldn't keep up with maintaining all of them. Hopefully the ones I kept are still illustrative.
|
||||
- no more plugins!
|
||||
|
||||
## 0.10.0 (2018-04-08)
|
||||
|
||||
## Breaking Changes
|
||||
### Breaking Changes
|
||||
Fixed rustc breakage in tarpc-plugins. These changes require a recent version of rustc.
|
||||
|
||||
## 0.10.0 (2018-03-26)
|
||||
|
||||
## Breaking Changes
|
||||
### Breaking Changes
|
||||
Updates bincode to version 1.0.
|
||||
|
||||
## 0.9.0 (2017-09-17)
|
||||
|
||||
## Breaking Changes
|
||||
### Breaking Changes
|
||||
Updates tarpc to use tarpc-plugins 0.2.
|
||||
|
||||
## 0.8.0 (2017-05-05)
|
||||
|
||||
## Breaking Changes
|
||||
### Breaking Changes
|
||||
This release updates tarpc to use serde 1.0.
|
||||
As such, users must also update to use serde 1.0.
|
||||
The serde 1.0 [release notes](https://github.com/serde-rs/serde/releases/tag/v1.0.0)
|
||||
@@ -28,7 +59,7 @@ clients. No breaking changes.
|
||||
|
||||
## 0.7.2 (2017-04-22)
|
||||
|
||||
## Breaking Changes
|
||||
### Breaking Changes
|
||||
This release updates tarpc-plugins to work with rustc master. Thus, older
|
||||
versions of rustc are no longer supported. We chose a minor version bump
|
||||
because it is still source-compatible with existing code using tarpc.
|
||||
@@ -39,7 +70,7 @@ This release was purely doc fixes. No breaking changes.
|
||||
|
||||
## 0.7 (2017-03-31)
|
||||
|
||||
## Breaking Changes
|
||||
### Breaking Changes
|
||||
This release is a complete overhaul to build tarpc on top of the tokio stack.
|
||||
It's safe to assume that everything broke with this release.
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin, test, use_extern_macros, proc_macro_path_invoc)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
#[cfg(test)]
|
||||
extern crate test;
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
extern crate tokio_core;
|
||||
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
#[cfg(test)]
|
||||
use test::Bencher;
|
||||
use tokio_core::reactor;
|
||||
|
||||
service! {
|
||||
rpc ack();
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Server;
|
||||
|
||||
impl FutureService for Server {
|
||||
type AckFut = futures::Finished<(), Never>;
|
||||
fn ack(&self) -> Self::AckFut {
|
||||
futures::finished(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[bench]
|
||||
fn latency(bencher: &mut Bencher) {
|
||||
let _ = env_logger::try_init();
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (handle, server) = Server
|
||||
.listen(
|
||||
"localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default(),
|
||||
)
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
let client = FutureClient::connect(
|
||||
handle.addr(),
|
||||
client::Options::default().handle(reactor.handle()),
|
||||
);
|
||||
let client = reactor.run(client).unwrap();
|
||||
|
||||
bencher.iter(|| reactor.run(client.ack()).unwrap());
|
||||
}
|
||||
40
bincode-transport/Cargo.toml
Normal file
40
bincode-transport/Cargo.toml
Normal file
@@ -0,0 +1,40 @@
|
||||
cargo-features = ["rename-dependency"]
|
||||
|
||||
[package]
|
||||
name = "tarpc-bincode-transport"
|
||||
version = "0.3.0"
|
||||
authors = ["Tim Kuehn <tikue@google.com>"]
|
||||
edition = '2018'
|
||||
license = "MIT"
|
||||
documentation = "https://docs.rs/tarpc-bincode-transport"
|
||||
homepage = "https://github.com/google/tarpc"
|
||||
repository = "https://github.com/google/tarpc"
|
||||
keywords = ["rpc", "network", "bincode", "serde", "tarpc"]
|
||||
categories = ["asynchronous", "network-programming"]
|
||||
readme = "../README.md"
|
||||
description = "A bincode-based transport for tarpc services."
|
||||
|
||||
[dependencies]
|
||||
bincode = { version = "1.0", features = ["i128"] }
|
||||
futures_legacy = { version = "0.1", package = "futures" }
|
||||
pin-utils = "0.1.0-alpha.3"
|
||||
rpc = { package = "tarpc-lib", version = "0.2", path = "../rpc", features = ["serde1"] }
|
||||
serde = "1.0"
|
||||
tokio-io = "0.1"
|
||||
async-bincode = "0.4"
|
||||
tokio-tcp = "0.1"
|
||||
|
||||
[target.'cfg(not(test))'.dependencies]
|
||||
futures-preview = { version = "0.3.0-alpha.9", features = ["compat"] }
|
||||
|
||||
[dev-dependencies]
|
||||
futures-preview = { version = "0.3.0-alpha.9", features = ["compat", "tokio-compat"] }
|
||||
env_logger = "0.5"
|
||||
humantime = "1.0"
|
||||
log = "0.4"
|
||||
rand = "0.5"
|
||||
tokio = "0.1"
|
||||
tokio-executor = "0.1"
|
||||
tokio-reactor = "0.1"
|
||||
tokio-serde = "0.2"
|
||||
tokio-timer = "0.2"
|
||||
1
bincode-transport/rustfmt.toml
Normal file
1
bincode-transport/rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
edition = "2018"
|
||||
150
bincode-transport/src/compat.rs
Normal file
150
bincode-transport/src/compat.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use futures::{compat::Stream01CompatExt, prelude::*, ready};
|
||||
use futures_legacy::{
|
||||
executor::{
|
||||
self as executor01, Notify as Notify01, NotifyHandle as NotifyHandle01,
|
||||
UnsafeNotify as UnsafeNotify01,
|
||||
},
|
||||
Async as Async01, AsyncSink as AsyncSink01, Sink as Sink01, Stream as Stream01,
|
||||
};
|
||||
use std::{
|
||||
pin::Pin,
|
||||
task::{self, LocalWaker, Poll},
|
||||
};
|
||||
|
||||
/// A shim to convert a 0.1 Sink + Stream to a 0.3 Sink + Stream.
|
||||
#[derive(Debug)]
|
||||
pub struct Compat<S, SinkItem> {
|
||||
staged_item: Option<SinkItem>,
|
||||
inner: S,
|
||||
}
|
||||
|
||||
impl<S, SinkItem> Compat<S, SinkItem> {
|
||||
/// Returns a new Compat.
|
||||
pub fn new(inner: S) -> Self {
|
||||
Compat {
|
||||
inner,
|
||||
staged_item: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwraps Compat, returning the inner value.
|
||||
pub fn into_inner(self) -> S {
|
||||
self.inner
|
||||
}
|
||||
|
||||
/// Returns a reference to the value wrapped by Compat.
|
||||
pub fn get_ref(&self) -> &S {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, SinkItem> Stream for Compat<S, SinkItem>
|
||||
where
|
||||
S: Stream01,
|
||||
{
|
||||
type Item = Result<S::Item, S::Error>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Option<Self::Item>> {
|
||||
unsafe {
|
||||
let inner = &mut Pin::get_mut_unchecked(self).inner;
|
||||
let mut compat = inner.compat();
|
||||
let compat = Pin::new_unchecked(&mut compat);
|
||||
match ready!(compat.poll_next(waker)) {
|
||||
None => Poll::Ready(None),
|
||||
Some(Ok(next)) => Poll::Ready(Some(Ok(next))),
|
||||
Some(Err(e)) => Poll::Ready(Some(Err(e))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, SinkItem> Sink for Compat<S, SinkItem>
|
||||
where
|
||||
S: Sink01<SinkItem = SinkItem>,
|
||||
{
|
||||
type SinkItem = SinkItem;
|
||||
type SinkError = S::SinkError;
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: SinkItem) -> Result<(), S::SinkError> {
|
||||
let me = unsafe { Pin::get_mut_unchecked(self) };
|
||||
assert!(me.staged_item.is_none());
|
||||
me.staged_item = Some(item);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Result<(), S::SinkError>> {
|
||||
let notify = &WakerToHandle(waker);
|
||||
|
||||
executor01::with_notify(notify, 0, move || {
|
||||
let me = unsafe { Pin::get_mut_unchecked(self) };
|
||||
match me.staged_item.take() {
|
||||
Some(staged_item) => match me.inner.start_send(staged_item) {
|
||||
Ok(AsyncSink01::Ready) => Poll::Ready(Ok(())),
|
||||
Ok(AsyncSink01::NotReady(item)) => {
|
||||
me.staged_item = Some(item);
|
||||
Poll::Pending
|
||||
}
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
},
|
||||
None => Poll::Ready(Ok(())),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Result<(), S::SinkError>> {
|
||||
let notify = &WakerToHandle(waker);
|
||||
|
||||
executor01::with_notify(notify, 0, move || {
|
||||
let me = unsafe { Pin::get_mut_unchecked(self) };
|
||||
match me.inner.poll_complete() {
|
||||
Ok(Async01::Ready(())) => Poll::Ready(Ok(())),
|
||||
Ok(Async01::NotReady) => Poll::Pending,
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Result<(), S::SinkError>> {
|
||||
let notify = &WakerToHandle(waker);
|
||||
|
||||
executor01::with_notify(notify, 0, move || {
|
||||
let me = unsafe { Pin::get_mut_unchecked(self) };
|
||||
match me.inner.close() {
|
||||
Ok(Async01::Ready(())) => Poll::Ready(Ok(())),
|
||||
Ok(Async01::NotReady) => Poll::Pending,
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct WakerToHandle<'a>(&'a LocalWaker);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NotifyWaker(task::Waker);
|
||||
|
||||
impl Notify01 for NotifyWaker {
|
||||
fn notify(&self, _: usize) {
|
||||
self.0.wake();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl UnsafeNotify01 for NotifyWaker {
|
||||
unsafe fn clone_raw(&self) -> NotifyHandle01 {
|
||||
let ptr = Box::new(NotifyWaker(self.0.clone()));
|
||||
|
||||
NotifyHandle01::new(Box::into_raw(ptr))
|
||||
}
|
||||
|
||||
unsafe fn drop_raw(&self) {
|
||||
let ptr: *const dyn UnsafeNotify01 = self;
|
||||
drop(Box::from_raw(ptr as *mut dyn UnsafeNotify01));
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<WakerToHandle<'a>> for NotifyHandle01 {
|
||||
fn from(handle: WakerToHandle<'a>) -> NotifyHandle01 {
|
||||
unsafe { NotifyWaker(handle.0.clone().into_waker()).clone_raw() }
|
||||
}
|
||||
}
|
||||
202
bincode-transport/src/lib.rs
Normal file
202
bincode-transport/src/lib.rs
Normal file
@@ -0,0 +1,202 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! A TCP [`Transport`] that serializes as bincode.
|
||||
|
||||
#![feature(
|
||||
futures_api,
|
||||
pin,
|
||||
arbitrary_self_types,
|
||||
await_macro,
|
||||
async_await
|
||||
)]
|
||||
#![deny(missing_docs, missing_debug_implementations)]
|
||||
|
||||
use self::compat::Compat;
|
||||
use async_bincode::{AsyncBincodeStream, AsyncDestination};
|
||||
use futures::{
|
||||
compat::{Compat01As03, Future01CompatExt, Stream01CompatExt},
|
||||
prelude::*,
|
||||
ready,
|
||||
};
|
||||
use pin_utils::unsafe_pinned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
error::Error,
|
||||
io,
|
||||
marker::PhantomData,
|
||||
net::SocketAddr,
|
||||
pin::Pin,
|
||||
task::{LocalWaker, Poll},
|
||||
};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_tcp::{TcpListener, TcpStream};
|
||||
|
||||
mod compat;
|
||||
|
||||
/// A transport that serializes to, and deserializes from, a [`TcpStream`].
|
||||
#[derive(Debug)]
|
||||
pub struct Transport<S, Item, SinkItem> {
|
||||
inner: Compat<AsyncBincodeStream<S, Item, SinkItem, AsyncDestination>, SinkItem>,
|
||||
}
|
||||
|
||||
impl<S, Item, SinkItem> Transport<S, Item, SinkItem> {
|
||||
/// Returns the transport underlying the bincode transport.
|
||||
pub fn into_inner(self) -> S {
|
||||
self.inner.into_inner().into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Item, SinkItem> Transport<S, Item, SinkItem> {
|
||||
unsafe_pinned!(
|
||||
inner: Compat<AsyncBincodeStream<S, Item, SinkItem, AsyncDestination>, SinkItem>
|
||||
);
|
||||
}
|
||||
|
||||
impl<S, Item, SinkItem> Stream for Transport<S, Item, SinkItem>
|
||||
where
|
||||
S: AsyncRead,
|
||||
Item: for<'a> Deserialize<'a>,
|
||||
{
|
||||
type Item = io::Result<Item>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Option<io::Result<Item>>> {
|
||||
match self.inner().poll_next(waker) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
Poll::Ready(Some(Ok(next))) => Poll::Ready(Some(Ok(next))),
|
||||
Poll::Ready(Some(Err(e))) => {
|
||||
Poll::Ready(Some(Err(io::Error::new(io::ErrorKind::Other, e))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Item, SinkItem> Sink for Transport<S, Item, SinkItem>
|
||||
where
|
||||
S: AsyncWrite,
|
||||
SinkItem: Serialize,
|
||||
{
|
||||
type SinkItem = SinkItem;
|
||||
type SinkError = io::Error;
|
||||
|
||||
fn start_send(mut self: Pin<&mut Self>, item: SinkItem) -> io::Result<()> {
|
||||
self.inner()
|
||||
.start_send(item)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
}
|
||||
|
||||
fn poll_ready(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<io::Result<()>> {
|
||||
convert(self.inner().poll_ready(waker))
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<io::Result<()>> {
|
||||
convert(self.inner().poll_flush(waker))
|
||||
}
|
||||
|
||||
fn poll_close(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<io::Result<()>> {
|
||||
convert(self.inner().poll_close(waker))
|
||||
}
|
||||
}
|
||||
|
||||
fn convert<E: Into<Box<Error + Send + Sync>>>(poll: Poll<Result<(), E>>) -> Poll<io::Result<()>> {
|
||||
match poll {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
|
||||
Poll::Ready(Err(e)) => Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, e))),
|
||||
}
|
||||
}
|
||||
|
||||
impl<Item, SinkItem> rpc::Transport for Transport<TcpStream, Item, SinkItem>
|
||||
where
|
||||
Item: for<'de> Deserialize<'de>,
|
||||
SinkItem: Serialize,
|
||||
{
|
||||
type Item = Item;
|
||||
type SinkItem = SinkItem;
|
||||
|
||||
fn peer_addr(&self) -> io::Result<SocketAddr> {
|
||||
self.inner.get_ref().get_ref().peer_addr()
|
||||
}
|
||||
|
||||
fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||
self.inner.get_ref().get_ref().local_addr()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new bincode transport that reads from and writes to `io`.
|
||||
pub fn new<Item, SinkItem>(io: TcpStream) -> Transport<TcpStream, Item, SinkItem>
|
||||
where
|
||||
Item: for<'de> Deserialize<'de>,
|
||||
SinkItem: Serialize,
|
||||
{
|
||||
Transport::from(io)
|
||||
}
|
||||
|
||||
impl<S, Item, SinkItem> From<S> for Transport<S, Item, SinkItem> {
|
||||
fn from(inner: S) -> Self {
|
||||
Transport {
|
||||
inner: Compat::new(AsyncBincodeStream::from(inner).for_async()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connects to `addr`, wrapping the connection in a bincode transport.
|
||||
pub async fn connect<Item, SinkItem>(
|
||||
addr: &SocketAddr,
|
||||
) -> io::Result<Transport<TcpStream, Item, SinkItem>>
|
||||
where
|
||||
Item: for<'de> Deserialize<'de>,
|
||||
SinkItem: Serialize,
|
||||
{
|
||||
Ok(new(await!(TcpStream::connect(addr).compat())?))
|
||||
}
|
||||
|
||||
/// Listens on `addr`, wrapping accepted connections in bincode transports.
|
||||
pub fn listen<Item, SinkItem>(addr: &SocketAddr) -> io::Result<Incoming<Item, SinkItem>>
|
||||
where
|
||||
Item: for<'de> Deserialize<'de>,
|
||||
SinkItem: Serialize,
|
||||
{
|
||||
let listener = TcpListener::bind(addr)?;
|
||||
let local_addr = listener.local_addr()?;
|
||||
let incoming = listener.incoming().compat();
|
||||
Ok(Incoming {
|
||||
incoming,
|
||||
local_addr,
|
||||
ghost: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// A [`TcpListener`] that wraps connections in bincode transports.
|
||||
#[derive(Debug)]
|
||||
pub struct Incoming<Item, SinkItem> {
|
||||
incoming: Compat01As03<tokio_tcp::Incoming>,
|
||||
local_addr: SocketAddr,
|
||||
ghost: PhantomData<(Item, SinkItem)>,
|
||||
}
|
||||
|
||||
impl<Item, SinkItem> Incoming<Item, SinkItem> {
|
||||
unsafe_pinned!(incoming: Compat01As03<tokio_tcp::Incoming>);
|
||||
|
||||
/// Returns the address being listened on.
|
||||
pub fn local_addr(&self) -> SocketAddr {
|
||||
self.local_addr
|
||||
}
|
||||
}
|
||||
|
||||
impl<Item, SinkItem> Stream for Incoming<Item, SinkItem>
|
||||
where
|
||||
Item: for<'a> Deserialize<'a>,
|
||||
SinkItem: Serialize,
|
||||
{
|
||||
type Item = io::Result<Transport<TcpStream, Item, SinkItem>>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Option<Self::Item>> {
|
||||
let next = ready!(self.incoming().poll_next(waker)?);
|
||||
Poll::Ready(next.map(|conn| Ok(new(conn))))
|
||||
}
|
||||
}
|
||||
110
bincode-transport/tests/bench.rs
Normal file
110
bincode-transport/tests/bench.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! Tests client/server control flow.
|
||||
|
||||
#![feature(
|
||||
test,
|
||||
integer_atomics,
|
||||
futures_api,
|
||||
generators,
|
||||
await_macro,
|
||||
async_await
|
||||
)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use self::test::stats::Stats;
|
||||
use futures::{compat::TokioDefaultSpawner, prelude::*};
|
||||
use rpc::{
|
||||
client, context,
|
||||
server::{Handler, Server},
|
||||
};
|
||||
use std::{
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
async fn bench() -> io::Result<()> {
|
||||
let listener = tarpc_bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||
let addr = listener.local_addr();
|
||||
|
||||
tokio_executor::spawn(
|
||||
Server::<u32, u32>::default()
|
||||
.incoming(listener)
|
||||
.take(1)
|
||||
.respond_with(|_ctx, request| futures::future::ready(Ok(request)))
|
||||
.unit_error()
|
||||
.boxed()
|
||||
.compat(),
|
||||
);
|
||||
|
||||
let conn = await!(tarpc_bincode_transport::connect(&addr))?;
|
||||
let client = &mut await!(client::new::<u32, u32, _>(client::Config::default(), conn))?;
|
||||
|
||||
let total = 10_000usize;
|
||||
let mut successful = 0u32;
|
||||
let mut unsuccessful = 0u32;
|
||||
let mut durations = vec![];
|
||||
for _ in 1..=total {
|
||||
let now = Instant::now();
|
||||
let response = await!(client.call(context::current(), 0u32));
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
match response {
|
||||
Ok(_) => successful += 1,
|
||||
Err(_) => unsuccessful += 1,
|
||||
};
|
||||
durations.push(elapsed);
|
||||
}
|
||||
|
||||
let durations_nanos = durations
|
||||
.iter()
|
||||
.map(|duration| duration.as_secs() as f64 * 1E9 + duration.subsec_nanos() as f64)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (lower, median, upper) = durations_nanos.quartiles();
|
||||
|
||||
println!("Of {} runs:", durations_nanos.len());
|
||||
println!("\tSuccessful: {}", successful);
|
||||
println!("\tUnsuccessful: {}", unsuccessful);
|
||||
println!(
|
||||
"\tMean: {:?}",
|
||||
Duration::from_nanos(durations_nanos.mean() as u64)
|
||||
);
|
||||
println!("\tMedian: {:?}", Duration::from_nanos(median as u64));
|
||||
println!(
|
||||
"\tStd Dev: {:?}",
|
||||
Duration::from_nanos(durations_nanos.std_dev() as u64)
|
||||
);
|
||||
println!(
|
||||
"\tMin: {:?}",
|
||||
Duration::from_nanos(durations_nanos.min() as u64)
|
||||
);
|
||||
println!(
|
||||
"\tMax: {:?}",
|
||||
Duration::from_nanos(durations_nanos.max() as u64)
|
||||
);
|
||||
println!(
|
||||
"\tQuartiles: ({:?}, {:?}, {:?})",
|
||||
Duration::from_nanos(lower as u64),
|
||||
Duration::from_nanos(median as u64),
|
||||
Duration::from_nanos(upper as u64)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bench_small_packet() -> io::Result<()> {
|
||||
env_logger::init();
|
||||
rpc::init(TokioDefaultSpawner);
|
||||
|
||||
tokio::run(bench().map_err(|e| panic!(e.to_string())).boxed().compat());
|
||||
println!("done");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
143
bincode-transport/tests/cancel.rs
Normal file
143
bincode-transport/tests/cancel.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! Tests client/server control flow.
|
||||
|
||||
#![feature(generators, await_macro, async_await, futures_api)]
|
||||
|
||||
use futures::{
|
||||
compat::{Future01CompatExt, TokioDefaultSpawner},
|
||||
prelude::*,
|
||||
stream,
|
||||
};
|
||||
use log::{info, trace};
|
||||
use rand::distributions::{Distribution, Normal};
|
||||
use rpc::{client, context, server::Server};
|
||||
use std::{
|
||||
io,
|
||||
time::{Duration, Instant, SystemTime},
|
||||
};
|
||||
use tokio::timer::Delay;
|
||||
|
||||
pub trait AsDuration {
|
||||
/// Delay of 0 if self is in the past
|
||||
fn as_duration(&self) -> Duration;
|
||||
}
|
||||
|
||||
impl AsDuration for SystemTime {
|
||||
fn as_duration(&self) -> Duration {
|
||||
self.duration_since(SystemTime::now()).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
async fn run() -> io::Result<()> {
|
||||
let listener = tarpc_bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||
let addr = listener.local_addr();
|
||||
let server = Server::<String, String>::default()
|
||||
.incoming(listener)
|
||||
.take(1)
|
||||
.for_each(async move |channel| {
|
||||
let channel = if let Ok(channel) = channel {
|
||||
channel
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let client_addr = *channel.client_addr();
|
||||
let handler = channel.respond_with(move |ctx, request| {
|
||||
// Sleep for a time sampled from a normal distribution with:
|
||||
// - mean: 1/2 the deadline.
|
||||
// - std dev: 1/2 the deadline.
|
||||
let deadline: Duration = ctx.deadline.as_duration();
|
||||
let deadline_millis = deadline.as_secs() * 1000 + deadline.subsec_millis() as u64;
|
||||
let distribution =
|
||||
Normal::new(deadline_millis as f64 / 2., deadline_millis as f64 / 2.);
|
||||
let delay_millis = distribution.sample(&mut rand::thread_rng()).max(0.);
|
||||
let delay = Duration::from_millis(delay_millis as u64);
|
||||
|
||||
trace!(
|
||||
"[{}/{}] Responding to request in {:?}.",
|
||||
ctx.trace_id(),
|
||||
client_addr,
|
||||
delay,
|
||||
);
|
||||
|
||||
let wait = Delay::new(Instant::now() + delay).compat();
|
||||
async move {
|
||||
await!(wait).unwrap();
|
||||
Ok(request)
|
||||
}
|
||||
});
|
||||
tokio_executor::spawn(handler.unit_error().boxed().compat());
|
||||
});
|
||||
|
||||
tokio_executor::spawn(server.unit_error().boxed().compat());
|
||||
|
||||
let conn = await!(tarpc_bincode_transport::connect(&addr))?;
|
||||
let client = await!(client::new::<String, String, _>(
|
||||
client::Config::default(),
|
||||
conn
|
||||
))?;
|
||||
|
||||
// Proxy service
|
||||
let listener = tarpc_bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||
let addr = listener.local_addr();
|
||||
let proxy_server = Server::<String, String>::default()
|
||||
.incoming(listener)
|
||||
.take(1)
|
||||
.for_each(move |channel| {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
let channel = if let Ok(channel) = channel {
|
||||
channel
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let client_addr = *channel.client_addr();
|
||||
let handler = channel.respond_with(move |ctx, request| {
|
||||
trace!("[{}/{}] Proxying request.", ctx.trace_id(), client_addr);
|
||||
let mut client = client.clone();
|
||||
async move { await!(client.call(ctx, request)) }
|
||||
});
|
||||
tokio_executor::spawn(handler.unit_error().boxed().compat());
|
||||
}
|
||||
});
|
||||
|
||||
tokio_executor::spawn(proxy_server.unit_error().boxed().compat());
|
||||
|
||||
let mut config = client::Config::default();
|
||||
config.max_in_flight_requests = 10;
|
||||
config.pending_request_buffer = 10;
|
||||
|
||||
let client = await!(client::new::<String, String, _>(
|
||||
config,
|
||||
await!(tarpc_bincode_transport::connect(&addr))?
|
||||
))?;
|
||||
|
||||
// Make 3 speculative requests, returning only the quickest.
|
||||
let mut clients: Vec<_> = (1..=3u32).map(|_| client.clone()).collect();
|
||||
let mut requests = vec![];
|
||||
for client in &mut clients {
|
||||
let mut ctx = context::current();
|
||||
ctx.deadline = SystemTime::now() + Duration::from_millis(200);
|
||||
let trace_id = *ctx.trace_id();
|
||||
let response = client.call(ctx, "ping".into());
|
||||
requests.push(response.map(move |r| (trace_id, r)));
|
||||
}
|
||||
let (fastest_response, _) = await!(stream::futures_unordered(requests).into_future());
|
||||
let (trace_id, resp) = fastest_response.unwrap();
|
||||
info!("[{}] fastest_response = {:?}", trace_id, resp);
|
||||
|
||||
Ok::<_, io::Error>(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancel_slower() -> io::Result<()> {
|
||||
env_logger::init();
|
||||
rpc::init(TokioDefaultSpawner);
|
||||
|
||||
tokio::run(run().boxed().map_err(|e| panic!(e)).compat());
|
||||
Ok(())
|
||||
}
|
||||
119
bincode-transport/tests/pushback.rs
Normal file
119
bincode-transport/tests/pushback.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! Tests client/server control flow.
|
||||
|
||||
#![feature(generators, await_macro, async_await, futures_api)]
|
||||
|
||||
use futures::{
|
||||
compat::{Future01CompatExt, TokioDefaultSpawner},
|
||||
prelude::*,
|
||||
};
|
||||
use log::{error, info, trace};
|
||||
use rand::distributions::{Distribution, Normal};
|
||||
use rpc::{client, context, server::Server};
|
||||
use std::{
|
||||
io,
|
||||
time::{Duration, Instant, SystemTime},
|
||||
};
|
||||
use tokio::timer::Delay;
|
||||
|
||||
pub trait AsDuration {
|
||||
/// Delay of 0 if self is in the past
|
||||
fn as_duration(&self) -> Duration;
|
||||
}
|
||||
|
||||
impl AsDuration for SystemTime {
|
||||
fn as_duration(&self) -> Duration {
|
||||
self.duration_since(SystemTime::now()).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
async fn run() -> io::Result<()> {
|
||||
let listener = tarpc_bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||
let addr = listener.local_addr();
|
||||
let server = Server::<String, String>::default()
|
||||
.incoming(listener)
|
||||
.take(1)
|
||||
.for_each(async move |channel| {
|
||||
let channel = if let Ok(channel) = channel {
|
||||
channel
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let client_addr = *channel.client_addr();
|
||||
let handler = channel.respond_with(move |ctx, request| {
|
||||
// Sleep for a time sampled from a normal distribution with:
|
||||
// - mean: 1/2 the deadline.
|
||||
// - std dev: 1/2 the deadline.
|
||||
let deadline: Duration = ctx.deadline.as_duration();
|
||||
let deadline_millis = deadline.as_secs() * 1000 + deadline.subsec_millis() as u64;
|
||||
let distribution =
|
||||
Normal::new(deadline_millis as f64 / 2., deadline_millis as f64 / 2.);
|
||||
let delay_millis = distribution.sample(&mut rand::thread_rng()).max(0.);
|
||||
let delay = Duration::from_millis(delay_millis as u64);
|
||||
|
||||
trace!(
|
||||
"[{}/{}] Responding to request in {:?}.",
|
||||
ctx.trace_id(),
|
||||
client_addr,
|
||||
delay,
|
||||
);
|
||||
|
||||
let sleep = Delay::new(Instant::now() + delay).compat();
|
||||
async {
|
||||
await!(sleep).unwrap();
|
||||
Ok(request)
|
||||
}
|
||||
});
|
||||
tokio_executor::spawn(handler.unit_error().boxed().compat());
|
||||
});
|
||||
|
||||
tokio_executor::spawn(server.unit_error().boxed().compat());
|
||||
|
||||
let mut config = client::Config::default();
|
||||
config.max_in_flight_requests = 10;
|
||||
config.pending_request_buffer = 10;
|
||||
|
||||
let conn = await!(tarpc_bincode_transport::connect(&addr))?;
|
||||
let client = await!(client::new::<String, String, _>(config, conn))?;
|
||||
|
||||
let clients = (1..=100u32).map(|_| client.clone()).collect::<Vec<_>>();
|
||||
for mut client in clients {
|
||||
let ctx = context::current();
|
||||
tokio_executor::spawn(
|
||||
async move {
|
||||
let trace_id = *ctx.trace_id();
|
||||
let response = client.call(ctx, "ping".into());
|
||||
match await!(response) {
|
||||
Ok(response) => info!("[{}] response: {}", trace_id, response),
|
||||
Err(e) => error!("[{}] request error: {:?}: {}", trace_id, e.kind(), e),
|
||||
}
|
||||
}
|
||||
.unit_error()
|
||||
.boxed()
|
||||
.compat(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ping_pong() -> io::Result<()> {
|
||||
env_logger::init();
|
||||
rpc::init(TokioDefaultSpawner);
|
||||
|
||||
tokio::run(
|
||||
run()
|
||||
.map_ok(|_| println!("done"))
|
||||
.map_err(|e| panic!(e.to_string()))
|
||||
.boxed()
|
||||
.compat(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
36
example-service/Cargo.toml
Normal file
36
example-service/Cargo.toml
Normal file
@@ -0,0 +1,36 @@
|
||||
cargo-features = ["rename-dependency"]
|
||||
|
||||
[package]
|
||||
name = "tarpc-example-service"
|
||||
version = "0.2.0"
|
||||
authors = ["Tim Kuehn <tikue@google.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
documentation = "https://docs.rs/tarpc-example-service"
|
||||
homepage = "https://github.com/google/tarpc"
|
||||
repository = "https://github.com/google/tarpc"
|
||||
keywords = ["rpc", "network", "server", "microservices", "example"]
|
||||
categories = ["asynchronous", "network-programming"]
|
||||
readme = "../README.md"
|
||||
description = "An example server built on tarpc."
|
||||
|
||||
[dependencies]
|
||||
bincode-transport = { package = "tarpc-bincode-transport", version = "0.3", path = "../bincode-transport" }
|
||||
clap = "2.0"
|
||||
futures-preview = { version = "0.3.0-alpha.9", features = ["compat", "tokio-compat"] }
|
||||
serde = { version = "1.0" }
|
||||
tarpc = { version = "0.14", path = "../tarpc", features = ["serde1"] }
|
||||
tokio = "0.1"
|
||||
tokio-executor = "0.1"
|
||||
|
||||
[lib]
|
||||
name = "service"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "server"
|
||||
path = "src/server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "client"
|
||||
path = "src/client.rs"
|
||||
79
example-service/src/client.rs
Normal file
79
example-service/src/client.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
#![feature(
|
||||
futures_api,
|
||||
pin,
|
||||
arbitrary_self_types,
|
||||
await_macro,
|
||||
async_await
|
||||
)]
|
||||
|
||||
use clap::{App, Arg};
|
||||
use futures::{compat::TokioDefaultSpawner, prelude::*};
|
||||
use std::{io, net::SocketAddr};
|
||||
use tarpc::{client, context};
|
||||
|
||||
async fn run(server_addr: SocketAddr, name: String) -> io::Result<()> {
|
||||
let transport = await!(bincode_transport::connect(&server_addr))?;
|
||||
|
||||
// new_stub is generated by the service! macro. Like Server, it takes a config and any
|
||||
// Transport as input, and returns a Client, also generated by the macro.
|
||||
// by the service mcro.
|
||||
let mut client = await!(service::new_stub(client::Config::default(), transport))?;
|
||||
|
||||
// The client has an RPC method for each RPC defined in service!. It takes the same args
|
||||
// as defined, with the addition of a Context, which is always the first arg. The Context
|
||||
// specifies a deadline and trace information which can be helpful in debugging requests.
|
||||
let hello = await!(client.hello(context::current(), name))?;
|
||||
|
||||
println!("{}", hello);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let flags = App::new("Hello Client")
|
||||
.version("0.1")
|
||||
.author("Tim <tikue@google.com>")
|
||||
.about("Say hello!")
|
||||
.arg(
|
||||
Arg::with_name("server_addr")
|
||||
.long("server_addr")
|
||||
.value_name("ADDRESS")
|
||||
.help("Sets the server address to connect to.")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("name")
|
||||
.short("n")
|
||||
.long("name")
|
||||
.value_name("STRING")
|
||||
.help("Sets the name to say hello to.")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
tarpc::init(TokioDefaultSpawner);
|
||||
|
||||
let server_addr = flags.value_of("server_addr").unwrap();
|
||||
let server_addr = server_addr
|
||||
.parse()
|
||||
.unwrap_or_else(|e| panic!(r#"--server_addr value "{}" invalid: {}"#, server_addr, e));
|
||||
|
||||
let name = flags.value_of("name").unwrap();
|
||||
|
||||
tarpc::init(TokioDefaultSpawner);
|
||||
|
||||
tokio::run(
|
||||
run(server_addr, name.into())
|
||||
.map_err(|e| eprintln!("Oh no: {}", e))
|
||||
.boxed()
|
||||
.compat(),
|
||||
);
|
||||
}
|
||||
21
example-service/src/lib.rs
Normal file
21
example-service/src/lib.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
#![feature(
|
||||
futures_api,
|
||||
pin,
|
||||
arbitrary_self_types,
|
||||
await_macro,
|
||||
async_await,
|
||||
proc_macro_hygiene
|
||||
)]
|
||||
|
||||
// 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! {
|
||||
/// Returns a greeting for name.
|
||||
rpc hello(name: String) -> String;
|
||||
}
|
||||
90
example-service/src/server.rs
Normal file
90
example-service/src/server.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
#![feature(
|
||||
futures_api,
|
||||
pin,
|
||||
arbitrary_self_types,
|
||||
await_macro,
|
||||
async_await
|
||||
)]
|
||||
|
||||
use clap::{App, Arg};
|
||||
use futures::{
|
||||
compat::TokioDefaultSpawner,
|
||||
future::{self, Ready},
|
||||
prelude::*,
|
||||
};
|
||||
use std::{io, net::SocketAddr};
|
||||
use tarpc::{
|
||||
context,
|
||||
server::{Handler, Server},
|
||||
};
|
||||
|
||||
// This is the type that implements the generated Service trait. It is the business logic
|
||||
// and is used to start the server.
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl service::Service for HelloServer {
|
||||
// Each defined rpc generates two items in the trait, a fn that serves the RPC, and
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(server_addr: SocketAddr) -> io::Result<()> {
|
||||
// bincode_transport is provided by the associated crate bincode-transport. It makes it easy
|
||||
// to start up a serde-powered bincode serialization strategy over TCP.
|
||||
let transport = bincode_transport::listen(&server_addr)?;
|
||||
|
||||
// The server is configured with the defaults.
|
||||
let server = Server::default()
|
||||
// Server can listen on any type that implements the Transport trait.
|
||||
.incoming(transport)
|
||||
// serve is generated by the service! macro. It takes as input any type implementing
|
||||
// the generated Service trait.
|
||||
.respond_with(service::serve(HelloServer));
|
||||
|
||||
await!(server);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let flags = App::new("Hello Server")
|
||||
.version("0.1")
|
||||
.author("Tim <tikue@google.com>")
|
||||
.about("Say hello!")
|
||||
.arg(
|
||||
Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.value_name("NUMBER")
|
||||
.help("Sets the port number to listen on")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let port = flags.value_of("port").unwrap();
|
||||
let port = port
|
||||
.parse()
|
||||
.unwrap_or_else(|e| panic!(r#"--port value "{}" invalid: {}"#, port, e));
|
||||
|
||||
tarpc::init(TokioDefaultSpawner);
|
||||
|
||||
tokio::run(
|
||||
run(([0, 0, 0, 0], port).into())
|
||||
.map_err(|e| eprintln!("Oh no: {}", e))
|
||||
.boxed()
|
||||
.compat(),
|
||||
);
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin, never_type, use_extern_macros, proc_macro_path_invoc)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate chrono;
|
||||
extern crate clap;
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate serde_bytes;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate tokio_core;
|
||||
extern crate futures_cpupool;
|
||||
|
||||
use clap::{Arg, App};
|
||||
use futures::{Future, Stream};
|
||||
use futures_cpupool::{CpuFuture, CpuPool};
|
||||
use std::{cmp, thread};
|
||||
use std::sync::{Arc, mpsc};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
use tokio_core::reactor;
|
||||
|
||||
service! {
|
||||
rpc read(size: u32) -> serde_bytes::ByteBuf;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Server {
|
||||
pool: CpuPool,
|
||||
request_count: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
fn new() -> Self {
|
||||
Server {
|
||||
pool: CpuPool::new_num_cpus(),
|
||||
request_count: Arc::new(AtomicUsize::new(1)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FutureService for Server {
|
||||
type ReadFut = CpuFuture<serde_bytes::ByteBuf, Never>;
|
||||
|
||||
fn read(&self, size: u32) -> Self::ReadFut {
|
||||
let request_number = self.request_count.fetch_add(1, Ordering::SeqCst);
|
||||
debug!("Server received read({}) no. {}", size, request_number);
|
||||
self.pool.spawn(futures::lazy(move || {
|
||||
let mut vec = Vec::with_capacity(size as usize);
|
||||
for i in 0..size {
|
||||
vec.push(((i % 2) << 8) as u8);
|
||||
}
|
||||
debug!("Server sending response no. {}", request_number);
|
||||
Ok(vec.into())
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const CHUNK_SIZE: u32 = 1 << 10;
|
||||
|
||||
trait Microseconds {
|
||||
fn microseconds(&self) -> i64;
|
||||
}
|
||||
|
||||
impl Microseconds for Duration {
|
||||
fn microseconds(&self) -> i64 {
|
||||
chrono::Duration::from_std(*self)
|
||||
.unwrap()
|
||||
.num_microseconds()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Stats {
|
||||
sum: Duration,
|
||||
count: u64,
|
||||
min: Option<Duration>,
|
||||
max: Option<Duration>,
|
||||
}
|
||||
|
||||
/// Spawns a `reactor::Core` running forever on a new thread.
|
||||
fn spawn_core() -> reactor::Remote {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let mut core = reactor::Core::new().unwrap();
|
||||
tx.send(core.handle().remote().clone()).unwrap();
|
||||
|
||||
// Run forever
|
||||
core.run(futures::empty::<(), !>()).unwrap();
|
||||
});
|
||||
rx.recv().unwrap()
|
||||
}
|
||||
|
||||
fn run_once(
|
||||
clients: Vec<FutureClient>,
|
||||
concurrency: u32,
|
||||
) -> impl Future<Item = (), Error = ()> + 'static {
|
||||
let start = Instant::now();
|
||||
futures::stream::futures_unordered(
|
||||
(0..concurrency as usize)
|
||||
.zip(clients.iter().enumerate().cycle())
|
||||
.map(|(iteration, (client_idx, client))| {
|
||||
let start = Instant::now();
|
||||
debug!("Client {} reading (iteration {})...", client_idx, iteration);
|
||||
client
|
||||
.read(CHUNK_SIZE)
|
||||
.map(move |_| (client_idx, iteration, start))
|
||||
}),
|
||||
).map(|(client_idx, iteration, start)| {
|
||||
let elapsed = start.elapsed();
|
||||
debug!(
|
||||
"Client {} received reply (iteration {}).",
|
||||
client_idx,
|
||||
iteration
|
||||
);
|
||||
elapsed
|
||||
})
|
||||
.map_err(|e| panic!(e))
|
||||
.fold(Stats::default(), move |mut stats, elapsed| {
|
||||
stats.sum += elapsed;
|
||||
stats.count += 1;
|
||||
stats.min = Some(cmp::min(stats.min.unwrap_or(elapsed), elapsed));
|
||||
stats.max = Some(cmp::max(stats.max.unwrap_or(elapsed), elapsed));
|
||||
Ok(stats)
|
||||
})
|
||||
.map(move |stats| {
|
||||
info!(
|
||||
"{} requests => Mean={}µs, Min={}µs, Max={}µs, Total={}µs",
|
||||
stats.count,
|
||||
stats.sum.microseconds() as f64 / stats.count as f64,
|
||||
stats.min.unwrap().microseconds(),
|
||||
stats.max.unwrap().microseconds(),
|
||||
start.elapsed().microseconds()
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let matches = App::new("Tarpc Concurrency")
|
||||
.about(
|
||||
"Demonstrates making concurrent requests to a tarpc service.",
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("concurrency")
|
||||
.short("c")
|
||||
.long("concurrency")
|
||||
.value_name("LEVEL")
|
||||
.help("Sets a custom concurrency level")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("clients")
|
||||
.short("n")
|
||||
.long("num_clients")
|
||||
.value_name("AMOUNT")
|
||||
.help("How many clients to distribute requests between")
|
||||
.takes_value(true),
|
||||
)
|
||||
.get_matches();
|
||||
let concurrency = matches
|
||||
.value_of("concurrency")
|
||||
.map(&str::parse)
|
||||
.map(Result::unwrap)
|
||||
.unwrap_or(10);
|
||||
let num_clients = matches
|
||||
.value_of("clients")
|
||||
.map(&str::parse)
|
||||
.map(Result::unwrap)
|
||||
.unwrap_or(4);
|
||||
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (handle, server) = Server::new()
|
||||
.listen(
|
||||
"localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default(),
|
||||
)
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
info!("Server listening on {}.", handle.addr());
|
||||
|
||||
let clients = (0..num_clients)
|
||||
// Spin up a couple threads to drive the clients.
|
||||
.map(|i| (i, spawn_core()))
|
||||
.map(|(i, remote)| {
|
||||
info!("Client {} connecting...", i);
|
||||
FutureClient::connect(handle.addr(), client::Options::default().remote(remote))
|
||||
.map_err(|e| panic!(e))
|
||||
});
|
||||
|
||||
let run = futures::collect(clients).and_then(|clients| run_once(clients, concurrency));
|
||||
|
||||
info!("Starting...");
|
||||
|
||||
reactor.run(run).unwrap();
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin, use_extern_macros, proc_macro_path_invoc)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate tokio_core;
|
||||
|
||||
use futures::{Future, future};
|
||||
use publisher::FutureServiceExt as PublisherExt;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::rc::Rc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use subscriber::FutureServiceExt as SubscriberExt;
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Message, Never};
|
||||
use tokio_core::reactor;
|
||||
|
||||
pub mod subscriber {
|
||||
service! {
|
||||
rpc receive(message: String);
|
||||
}
|
||||
}
|
||||
|
||||
pub mod publisher {
|
||||
use std::net::SocketAddr;
|
||||
use tarpc::util::Message;
|
||||
|
||||
service! {
|
||||
rpc broadcast(message: String);
|
||||
rpc subscribe(id: u32, address: SocketAddr) | Message;
|
||||
rpc unsubscribe(id: u32);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Subscriber {
|
||||
id: u32,
|
||||
}
|
||||
|
||||
impl subscriber::FutureService for Subscriber {
|
||||
type ReceiveFut = Result<(), Never>;
|
||||
|
||||
fn receive(&self, message: String) -> Self::ReceiveFut {
|
||||
println!("{} received message: {}", self.id, message);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Subscriber {
|
||||
fn listen(id: u32, handle: &reactor::Handle, options: server::Options) -> server::Handle {
|
||||
let (server_handle, server) = Subscriber { id: id }
|
||||
.listen("localhost:0".first_socket_addr(), handle, options)
|
||||
.unwrap();
|
||||
handle.spawn(server);
|
||||
server_handle
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Publisher {
|
||||
clients: Rc<RefCell<HashMap<u32, subscriber::FutureClient>>>,
|
||||
}
|
||||
|
||||
impl Publisher {
|
||||
fn new() -> Publisher {
|
||||
Publisher {
|
||||
clients: Rc::new(RefCell::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl publisher::FutureService for Publisher {
|
||||
type BroadcastFut = Box<Future<Item = (), Error = Never>>;
|
||||
|
||||
fn broadcast(&self, message: String) -> Self::BroadcastFut {
|
||||
let acks = self.clients
|
||||
.borrow()
|
||||
.values()
|
||||
.map(move |client| client.receive(message.clone())
|
||||
// Ignore failing subscribers. In a real pubsub,
|
||||
// you'd want to continually retry until subscribers
|
||||
// ack.
|
||||
.then(|_| Ok(())))
|
||||
// Collect to a vec to end the borrow on `self.clients`.
|
||||
.collect::<Vec<_>>();
|
||||
Box::new(future::join_all(acks).map(|_| ()))
|
||||
}
|
||||
|
||||
type SubscribeFut = Box<Future<Item = (), Error = Message>>;
|
||||
|
||||
fn subscribe(&self, id: u32, address: SocketAddr) -> Self::SubscribeFut {
|
||||
let clients = Rc::clone(&self.clients);
|
||||
Box::new(
|
||||
subscriber::FutureClient::connect(address, client::Options::default())
|
||||
.map(move |subscriber| {
|
||||
println!("Subscribing {}.", id);
|
||||
clients.borrow_mut().insert(id, subscriber);
|
||||
()
|
||||
})
|
||||
.map_err(|e| e.to_string().into()),
|
||||
)
|
||||
}
|
||||
|
||||
type UnsubscribeFut = Box<Future<Item = (), Error = Never>>;
|
||||
|
||||
fn unsubscribe(&self, id: u32) -> Self::UnsubscribeFut {
|
||||
println!("Unsubscribing {}", id);
|
||||
self.clients.borrow_mut().remove(&id).unwrap();
|
||||
Box::new(futures::finished(()))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (publisher_handle, server) = Publisher::new()
|
||||
.listen(
|
||||
"localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default(),
|
||||
)
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
|
||||
let subscriber1 = Subscriber::listen(0, &reactor.handle(), server::Options::default());
|
||||
let subscriber2 = Subscriber::listen(1, &reactor.handle(), server::Options::default());
|
||||
|
||||
let publisher = reactor
|
||||
.run(publisher::FutureClient::connect(
|
||||
publisher_handle.addr(),
|
||||
client::Options::default(),
|
||||
))
|
||||
.unwrap();
|
||||
reactor
|
||||
.run(
|
||||
publisher
|
||||
.subscribe(0, subscriber1.addr())
|
||||
.and_then(|_| publisher.subscribe(1, subscriber2.addr()))
|
||||
.map_err(|e| panic!(e))
|
||||
.and_then(|_| {
|
||||
println!("Broadcasting...");
|
||||
publisher.broadcast("hello to all".to_string())
|
||||
})
|
||||
.and_then(|_| publisher.unsubscribe(1))
|
||||
.and_then(|_| publisher.broadcast("hi again".to_string())),
|
||||
)
|
||||
.unwrap();
|
||||
thread::sleep(Duration::from_millis(300));
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin, use_extern_macros, proc_macro_path_invoc)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tarpc::sync::{client, server};
|
||||
use tarpc::sync::client::ClientExt;
|
||||
|
||||
service! {
|
||||
rpc hello(name: String) -> String | NoNameGiven;
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct NoNameGiven;
|
||||
|
||||
impl fmt::Display for NoNameGiven {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.description())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for NoNameGiven {
|
||||
fn description(&self) -> &str {
|
||||
r#"The empty String, "", is not a valid argument to rpc `hello`."#
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl SyncService for HelloServer {
|
||||
fn hello(&self, name: String) -> Result<String, NoNameGiven> {
|
||||
if name == "" {
|
||||
Err(NoNameGiven)
|
||||
} else {
|
||||
Ok(format!("Hello, {}!", name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let handle = HelloServer
|
||||
.listen("localhost:10000", server::Options::default())
|
||||
.unwrap();
|
||||
tx.send(handle.addr()).unwrap();
|
||||
handle.run();
|
||||
});
|
||||
let client = SyncClient::connect(rx.recv().unwrap(), client::Options::default()).unwrap();
|
||||
println!("{}", client.hello("Mom".to_string()).unwrap());
|
||||
println!("{}", client.hello("".to_string()).unwrap_err());
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin, use_extern_macros, proc_macro_path_invoc)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate tokio_core;
|
||||
|
||||
use futures::Future;
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
use tokio_core::reactor;
|
||||
|
||||
service! {
|
||||
rpc hello(name: String) -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl FutureService for HelloServer {
|
||||
type HelloFut = Result<String, Never>;
|
||||
|
||||
fn hello(&self, name: String) -> Self::HelloFut {
|
||||
Ok(format!("Hello, {}!", name))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (handle, server) = HelloServer
|
||||
.listen(
|
||||
"localhost:10000".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default(),
|
||||
)
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
|
||||
let options = client::Options::default().handle(reactor.handle());
|
||||
reactor
|
||||
.run(
|
||||
FutureClient::connect(handle.addr(), options)
|
||||
.map_err(tarpc::Error::from)
|
||||
.and_then(|client| client.hello("Mom".to_string()))
|
||||
.map(|resp| println!("{}", resp)),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
// required by `FutureClient` (not used directly in this example)
|
||||
#![feature(plugin, use_extern_macros, proc_macro_path_invoc)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tarpc::sync::{client, server};
|
||||
use tarpc::sync::client::ClientExt;
|
||||
use tarpc::util::Never;
|
||||
|
||||
service! {
|
||||
rpc hello(name: String) -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl SyncService for HelloServer {
|
||||
fn hello(&self, name: String) -> Result<String, Never> {
|
||||
Ok(format!(
|
||||
"Hello from thread {}, {}!",
|
||||
thread::current().name().unwrap(),
|
||||
name
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let handle = HelloServer
|
||||
.listen("localhost:0", server::Options::default())
|
||||
.unwrap();
|
||||
tx.send(handle.addr()).unwrap();
|
||||
handle.run();
|
||||
});
|
||||
let client = SyncClient::connect(rx.recv().unwrap(), client::Options::default()).unwrap();
|
||||
println!("{}", client.hello("Mom".to_string()).unwrap());
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin, use_extern_macros, proc_macro_path_invoc)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate env_logger;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate futures;
|
||||
extern crate tokio_core;
|
||||
|
||||
use add::{FutureService as AddFutureService, FutureServiceExt as AddExt};
|
||||
use double::{FutureService as DoubleFutureService, FutureServiceExt as DoubleExt};
|
||||
use futures::{Future, Stream};
|
||||
use tarpc::future::{client, server};
|
||||
use tarpc::future::client::ClientExt as Fc;
|
||||
use tarpc::util::{FirstSocketAddr, Message, Never};
|
||||
use tokio_core::reactor;
|
||||
|
||||
pub mod add {
|
||||
service! {
|
||||
/// Add two ints together.
|
||||
rpc add(x: i32, y: i32) -> i32;
|
||||
}
|
||||
}
|
||||
|
||||
pub mod double {
|
||||
use tarpc::util::Message;
|
||||
|
||||
service! {
|
||||
/// 2 * x
|
||||
rpc double(x: i32) -> i32 | Message;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AddServer;
|
||||
|
||||
impl AddFutureService for AddServer {
|
||||
type AddFut = Result<i32, Never>;
|
||||
|
||||
fn add(&self, x: i32, y: i32) -> Self::AddFut {
|
||||
Ok(x + y)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DoubleServer {
|
||||
client: add::FutureClient,
|
||||
}
|
||||
|
||||
impl DoubleServer {
|
||||
fn new(client: add::FutureClient) -> Self {
|
||||
DoubleServer { client: client }
|
||||
}
|
||||
}
|
||||
|
||||
impl DoubleFutureService for DoubleServer {
|
||||
type DoubleFut = Box<Future<Item=i32, Error=Message>>;
|
||||
|
||||
fn double(&self, x: i32) -> Self::DoubleFut {
|
||||
Box::new(self.client
|
||||
.add(x, x)
|
||||
.map_err(|e| e.to_string().into()))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (add, server) = AddServer
|
||||
.listen(
|
||||
"localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default(),
|
||||
)
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
|
||||
let options = client::Options::default().handle(reactor.handle());
|
||||
let add_client = reactor
|
||||
.run(add::FutureClient::connect(add.addr(), options))
|
||||
.unwrap();
|
||||
|
||||
let (double, server) = DoubleServer::new(add_client)
|
||||
.listen(
|
||||
"localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default(),
|
||||
)
|
||||
.unwrap();
|
||||
reactor.handle().spawn(server);
|
||||
|
||||
let double_client = reactor
|
||||
.run(double::FutureClient::connect(
|
||||
double.addr(),
|
||||
client::Options::default(),
|
||||
))
|
||||
.unwrap();
|
||||
reactor
|
||||
.run(
|
||||
futures::stream::futures_unordered((0..5).map(|i| double_client.double(i)))
|
||||
.map_err(|e| println!("{}", e))
|
||||
.for_each(|i| {
|
||||
println!("{:?}", i);
|
||||
Ok(())
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin, use_extern_macros, proc_macro_path_invoc)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
extern crate env_logger;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
|
||||
use add::{SyncService as AddSyncService, SyncServiceExt as AddExt};
|
||||
use double::{SyncService as DoubleSyncService, SyncServiceExt as DoubleExt};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tarpc::sync::{client, server};
|
||||
use tarpc::sync::client::ClientExt as Fc;
|
||||
use tarpc::util::{FirstSocketAddr, Message, Never};
|
||||
|
||||
pub mod add {
|
||||
service! {
|
||||
/// Add two ints together.
|
||||
rpc add(x: i32, y: i32) -> i32;
|
||||
}
|
||||
}
|
||||
|
||||
pub mod double {
|
||||
use tarpc::util::Message;
|
||||
|
||||
service! {
|
||||
/// 2 * x
|
||||
rpc double(x: i32) -> i32 | Message;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AddServer;
|
||||
|
||||
impl AddSyncService for AddServer {
|
||||
fn add(&self, x: i32, y: i32) -> Result<i32, Never> {
|
||||
Ok(x + y)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DoubleServer {
|
||||
client: add::SyncClient,
|
||||
}
|
||||
|
||||
impl DoubleServer {
|
||||
fn new(client: add::SyncClient) -> Self {
|
||||
DoubleServer { client: client }
|
||||
}
|
||||
}
|
||||
|
||||
impl DoubleSyncService for DoubleServer {
|
||||
fn double(&self, x: i32) -> Result<i32, Message> {
|
||||
self.client.add(x, x).map_err(|e| e.to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let handle = AddServer
|
||||
.listen(
|
||||
"localhost:0".first_socket_addr(),
|
||||
server::Options::default(),
|
||||
)
|
||||
.unwrap();
|
||||
tx.send(handle.addr()).unwrap();
|
||||
handle.run();
|
||||
});
|
||||
|
||||
|
||||
let add = rx.recv().unwrap();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let add_client = add::SyncClient::connect(add, client::Options::default()).unwrap();
|
||||
let handle = DoubleServer::new(add_client)
|
||||
.listen(
|
||||
"localhost:0".first_socket_addr(),
|
||||
server::Options::default(),
|
||||
)
|
||||
.unwrap();
|
||||
tx.send(handle.addr()).unwrap();
|
||||
handle.run();
|
||||
});
|
||||
let double = rx.recv().unwrap();
|
||||
|
||||
let double_client = double::SyncClient::connect(double, client::Options::default()).unwrap();
|
||||
for i in 0..5 {
|
||||
let doubled = double_client.double(i).unwrap();
|
||||
println!("{:?}", doubled);
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin, use_extern_macros, proc_macro_path_invoc)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate env_logger;
|
||||
extern crate serde_bytes;
|
||||
extern crate tokio_core;
|
||||
|
||||
use std::io::{Read, Write, stdout};
|
||||
use std::net;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time;
|
||||
use tarpc::future::server;
|
||||
use tarpc::sync::client::{self, ClientExt};
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
use tokio_core::reactor;
|
||||
|
||||
lazy_static! {
|
||||
static ref BUF: serde_bytes::ByteBuf = gen_vec(CHUNK_SIZE as usize).into();
|
||||
}
|
||||
|
||||
fn gen_vec(size: usize) -> Vec<u8> {
|
||||
let mut vec: Vec<u8> = Vec::with_capacity(size);
|
||||
for i in 0..size {
|
||||
vec.push(((i % 2) << 8) as u8);
|
||||
}
|
||||
vec
|
||||
}
|
||||
|
||||
service! {
|
||||
rpc read() -> serde_bytes::ByteBuf;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Server;
|
||||
|
||||
impl FutureService for Server {
|
||||
type ReadFut = Result<serde_bytes::ByteBuf, Never>;
|
||||
|
||||
fn read(&self) -> Self::ReadFut {
|
||||
Ok(BUF.clone())
|
||||
}
|
||||
}
|
||||
|
||||
const CHUNK_SIZE: u32 = 1 << 19;
|
||||
|
||||
fn bench_tarpc(target: u64) {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (addr, server) = Server
|
||||
.listen(
|
||||
"localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default(),
|
||||
)
|
||||
.unwrap();
|
||||
tx.send(addr).unwrap();
|
||||
reactor.run(server).unwrap();
|
||||
});
|
||||
let client =
|
||||
SyncClient::connect(rx.recv().unwrap().addr(), client::Options::default()).unwrap();
|
||||
let start = time::Instant::now();
|
||||
let mut nread = 0;
|
||||
while nread < target {
|
||||
nread += client.read().unwrap().len() as u64;
|
||||
print!(".");
|
||||
stdout().flush().unwrap();
|
||||
}
|
||||
println!("done");
|
||||
let duration = time::Instant::now() - start;
|
||||
println!(
|
||||
"TARPC: {}MB/s",
|
||||
(target as f64 / (1024f64 * 1024f64)) /
|
||||
(duration.as_secs() as f64 + duration.subsec_nanos() as f64 / 10E9)
|
||||
);
|
||||
}
|
||||
|
||||
fn bench_tcp(target: u64) {
|
||||
let l = net::TcpListener::bind("localhost:0").unwrap();
|
||||
let addr = l.local_addr().unwrap();
|
||||
thread::spawn(move || {
|
||||
let (mut stream, _) = l.accept().unwrap();
|
||||
while let Ok(_) = stream.write_all(&*BUF) {}
|
||||
});
|
||||
let mut stream = net::TcpStream::connect(&addr).unwrap();
|
||||
let mut buf = vec![0; CHUNK_SIZE as usize];
|
||||
let start = time::Instant::now();
|
||||
let mut nread = 0;
|
||||
while nread < target {
|
||||
stream.read_exact(&mut buf[..]).unwrap();
|
||||
nread += CHUNK_SIZE as u64;
|
||||
print!(".");
|
||||
stdout().flush().unwrap();
|
||||
}
|
||||
println!("done");
|
||||
let duration = time::Instant::now() - start;
|
||||
println!(
|
||||
"TCP: {}MB/s",
|
||||
(target as f64 / (1024f64 * 1024f64)) /
|
||||
(duration.as_secs() as f64 + duration.subsec_nanos() as f64 / 10E9)
|
||||
);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let _ = *BUF; // To non-lazily initialize it.
|
||||
bench_tcp(256 << 20);
|
||||
bench_tarpc(256 << 20);
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
#![feature(plugin, use_extern_macros, proc_macro_path_invoc)]
|
||||
#![plugin(tarpc_plugins)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate tarpc;
|
||||
extern crate env_logger;
|
||||
extern crate tokio_core;
|
||||
|
||||
use bar::FutureServiceExt as BarExt;
|
||||
use baz::FutureServiceExt as BazExt;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tarpc::future::server;
|
||||
use tarpc::sync::client;
|
||||
use tarpc::sync::client::ClientExt;
|
||||
use tarpc::util::{FirstSocketAddr, Never};
|
||||
use tokio_core::reactor;
|
||||
|
||||
mod bar {
|
||||
service! {
|
||||
rpc bar(i: i32) -> i32;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Bar;
|
||||
impl bar::FutureService for Bar {
|
||||
type BarFut = Result<i32, Never>;
|
||||
|
||||
fn bar(&self, i: i32) -> Self::BarFut {
|
||||
Ok(i)
|
||||
}
|
||||
}
|
||||
|
||||
mod baz {
|
||||
service! {
|
||||
rpc baz(s: String) -> String;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Baz;
|
||||
impl baz::FutureService for Baz {
|
||||
type BazFut = Result<String, Never>;
|
||||
|
||||
fn baz(&self, s: String) -> Self::BazFut {
|
||||
Ok(format!("Hello, {}!", s))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let bar_client = {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (handle, server) = Bar.listen(
|
||||
"localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default(),
|
||||
).unwrap();
|
||||
tx.send(handle).unwrap();
|
||||
reactor.run(server).unwrap();
|
||||
});
|
||||
let handle = rx.recv().unwrap();
|
||||
bar::SyncClient::connect(handle.addr(), client::Options::default()).unwrap()
|
||||
};
|
||||
|
||||
let baz_client = {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let mut reactor = reactor::Core::new().unwrap();
|
||||
let (handle, server) = Baz.listen(
|
||||
"localhost:0".first_socket_addr(),
|
||||
&reactor.handle(),
|
||||
server::Options::default(),
|
||||
).unwrap();
|
||||
tx.send(handle).unwrap();
|
||||
reactor.run(server).unwrap();
|
||||
});
|
||||
let handle = rx.recv().unwrap();
|
||||
baz::SyncClient::connect(handle.addr(), client::Options::default()).unwrap()
|
||||
};
|
||||
|
||||
|
||||
info!("Result: {:?}", bar_client.bar(17));
|
||||
|
||||
let total = 20;
|
||||
for i in 1..(total + 1) {
|
||||
if i % 2 == 0 {
|
||||
info!("Result 1: {:?}", bar_client.bar(i));
|
||||
} else {
|
||||
info!("Result 2: {:?}", baz_client.baz(i.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
info!("Done.");
|
||||
}
|
||||
@@ -91,11 +91,8 @@ if [ "$?" == 0 ]; then
|
||||
|
||||
try_run "Building ... " cargo build --color=always
|
||||
try_run "Testing ... " cargo test --color=always
|
||||
try_run "Benching ... " cargo bench --color=always
|
||||
try_run "Doc Test ... " cargo clean && cargo build --tests && rustdoc --test README.md --edition 2018 -L target/debug/deps -Z unstable-options
|
||||
|
||||
try_run "Building with tls ... " cargo build --color=always --features tls
|
||||
try_run "Testing with tls ... " cargo test --color=always --features tls
|
||||
try_run "Benching with tls ... " cargo bench --color=always --features tls
|
||||
fi
|
||||
|
||||
exit $PREPUSH_RESULT
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
[package]
|
||||
name = "tarpc-plugins"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
authors = ["Adam Wright <adam.austin.wright@gmail.com>", "Tim Kuehn <timothy.j.kuehn@gmail.com>"]
|
||||
license = "MIT"
|
||||
documentation = "https://docs.rs/tarpc"
|
||||
documentation = "https://docs.rs/tarpc-plugins"
|
||||
homepage = "https://github.com/google/tarpc"
|
||||
repository = "https://github.com/google/tarpc"
|
||||
keywords = ["rpc", "network", "server", "api", "tls"]
|
||||
keywords = ["rpc", "network", "server", "api", "microservices"]
|
||||
categories = ["asynchronous", "network-programming"]
|
||||
readme = "../../README.md"
|
||||
description = "Plugins for tarpc, an RPC framework for Rust with a focus on ease of use."
|
||||
readme = "../README.md"
|
||||
description = "Proc macros for tarpc."
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "google/tarpc" }
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.7"
|
||||
syn = { version = "0.15", features = ["full", "extra-traits"] }
|
||||
quote = "0.6"
|
||||
proc-macro2 = "0.4"
|
||||
|
||||
[lib]
|
||||
plugin = true
|
||||
proc-macro = true
|
||||
1
plugins/rustfmt.toml
Normal file
1
plugins/rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
edition = "2018"
|
||||
90
plugins/src/lib.rs
Normal file
90
plugins/src/lib.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
extern crate itertools;
|
||||
extern crate proc_macro;
|
||||
extern crate proc_macro2;
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
use itertools::Itertools;
|
||||
use proc_macro2::Span;
|
||||
use quote::ToTokens;
|
||||
use std::str::FromStr;
|
||||
use syn::{parse, Ident, TraitItemType, TypePath};
|
||||
|
||||
#[proc_macro]
|
||||
pub fn snake_to_camel(input: TokenStream) -> TokenStream {
|
||||
let i = input.clone();
|
||||
let mut assoc_type = parse::<TraitItemType>(input)
|
||||
.unwrap_or_else(|_| panic!("Could not parse trait item from:\n{}", i));
|
||||
|
||||
let old_ident = convert(&mut assoc_type.ident);
|
||||
|
||||
for mut attr in &mut assoc_type.attrs {
|
||||
if let Some(pair) = attr.path.segments.first() {
|
||||
if pair.value().ident == "doc" {
|
||||
attr.tts = proc_macro2::TokenStream::from_str(
|
||||
&attr.tts.to_string().replace("{}", &old_ident),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assoc_type.into_token_stream().into()
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn ty_snake_to_camel(input: TokenStream) -> TokenStream {
|
||||
let mut path = parse::<TypePath>(input).unwrap();
|
||||
|
||||
// Only capitalize the final segment
|
||||
convert(&mut path.path.segments.last_mut().unwrap().into_value().ident);
|
||||
|
||||
path.into_token_stream().into()
|
||||
}
|
||||
|
||||
/// Converts an ident in-place to CamelCase and returns the previous ident.
|
||||
fn convert(ident: &mut Ident) -> String {
|
||||
let ident_str = ident.to_string();
|
||||
let mut camel_ty = String::new();
|
||||
|
||||
{
|
||||
// Find the first non-underscore and add it capitalized.
|
||||
let mut chars = ident_str.chars();
|
||||
|
||||
// Find the first non-underscore char, uppercase it, and append it.
|
||||
// Guaranteed to succeed because all idents must have at least one non-underscore char.
|
||||
camel_ty.extend(chars.find(|&c| c != '_').unwrap().to_uppercase());
|
||||
|
||||
// When we find an underscore, we remove it and capitalize the next char. To do this,
|
||||
// we need to ensure the next char is not another underscore.
|
||||
let mut chars = chars.coalesce(|c1, c2| {
|
||||
if c1 == '_' && c2 == '_' {
|
||||
Ok(c1)
|
||||
} else {
|
||||
Err((c1, c2))
|
||||
}
|
||||
});
|
||||
|
||||
while let Some(c) = chars.next() {
|
||||
if c != '_' {
|
||||
camel_ty.push(c);
|
||||
} else if let Some(c) = chars.next() {
|
||||
camel_ty.extend(c.to_uppercase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The Fut suffix is hardcoded right now; this macro isn't really meant to be general-purpose.
|
||||
camel_ty.push_str("Fut");
|
||||
|
||||
*ident = Ident::new(&camel_ty, Span::call_site());
|
||||
ident_str
|
||||
}
|
||||
38
rpc/Cargo.toml
Normal file
38
rpc/Cargo.toml
Normal file
@@ -0,0 +1,38 @@
|
||||
cargo-features = ["rename-dependency"]
|
||||
|
||||
[package]
|
||||
name = "tarpc-lib"
|
||||
version = "0.2.0"
|
||||
authors = ["Tim Kuehn <tikue@google.com>"]
|
||||
edition = '2018'
|
||||
license = "MIT"
|
||||
documentation = "https://docs.rs/tarpc-lib"
|
||||
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"
|
||||
description = "An RPC framework for Rust with a focus on ease of use."
|
||||
|
||||
[features]
|
||||
default = []
|
||||
serde1 = ["trace/serde", "serde", "serde/derive"]
|
||||
|
||||
[dependencies]
|
||||
fnv = "1.0"
|
||||
humantime = "1.0"
|
||||
log = "0.4"
|
||||
pin-utils = "0.1.0-alpha.3"
|
||||
rand = "0.5"
|
||||
tokio-timer = "0.2"
|
||||
trace = { package = "tarpc-trace", version = "0.1", path = "../trace" }
|
||||
serde = { optional = true, version = "1.0" }
|
||||
|
||||
[target.'cfg(not(test))'.dependencies]
|
||||
futures-preview = { version = "0.3.0-alpha.9", features = ["compat"] }
|
||||
|
||||
[dev-dependencies]
|
||||
futures-preview = { version = "0.3.0-alpha.9", features = ["compat", "tokio-compat"] }
|
||||
futures-test-preview = { version = "0.3.0-alpha.9" }
|
||||
env_logger = "0.5"
|
||||
tokio = "0.1"
|
||||
1
rpc/rustfmt.toml
Normal file
1
rpc/rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
edition = "2018"
|
||||
945
rpc/src/client/channel.rs
Normal file
945
rpc/src/client/channel.rs
Normal file
@@ -0,0 +1,945 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use crate::{
|
||||
context,
|
||||
util::{deadline_compat, AsDuration, Compact},
|
||||
ClientMessage, ClientMessageKind, Request, Response, Transport,
|
||||
};
|
||||
use fnv::FnvHashMap;
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
prelude::*,
|
||||
ready,
|
||||
stream::Fuse,
|
||||
task::LocalWaker,
|
||||
Poll,
|
||||
};
|
||||
use humantime::format_rfc3339;
|
||||
use log::{debug, error, info, trace};
|
||||
use pin_utils::{unsafe_pinned, unsafe_unpinned};
|
||||
use std::{
|
||||
io,
|
||||
marker::{self, Unpin},
|
||||
net::SocketAddr,
|
||||
pin::Pin,
|
||||
sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
Arc,
|
||||
},
|
||||
time::Instant,
|
||||
};
|
||||
use trace::SpanId;
|
||||
|
||||
use super::Config;
|
||||
|
||||
/// Handles communication from the client to request dispatch.
|
||||
#[derive(Debug)]
|
||||
pub struct Channel<Req, Resp> {
|
||||
to_dispatch: mpsc::Sender<DispatchRequest<Req, Resp>>,
|
||||
/// Channel to send a cancel message to the dispatcher.
|
||||
cancellation: RequestCancellation,
|
||||
/// The ID to use for the next request to stage.
|
||||
next_request_id: Arc<AtomicU64>,
|
||||
server_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl<Req, Resp> Clone for Channel<Req, Resp> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
to_dispatch: self.to_dispatch.clone(),
|
||||
cancellation: self.cancellation.clone(),
|
||||
next_request_id: self.next_request_id.clone(),
|
||||
server_addr: self.server_addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A future returned by [`Channel::send`] that resolves to a server response.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
struct Send<'a, Req, Resp> {
|
||||
fut: MapOkDispatchResponse<
|
||||
MapErrConnectionReset<futures::sink::Send<'a, mpsc::Sender<DispatchRequest<Req, Resp>>>>,
|
||||
Resp,
|
||||
>,
|
||||
}
|
||||
|
||||
impl<'a, Req, Resp> Send<'a, Req, Resp> {
|
||||
unsafe_pinned!(
|
||||
fut: MapOkDispatchResponse<
|
||||
MapErrConnectionReset<
|
||||
futures::sink::Send<'a, mpsc::Sender<DispatchRequest<Req, Resp>>>,
|
||||
>,
|
||||
Resp,
|
||||
>
|
||||
);
|
||||
}
|
||||
|
||||
impl<'a, Req, Resp> Future for Send<'a, Req, Resp> {
|
||||
type Output = io::Result<DispatchResponse<Resp>>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, lw: &LocalWaker) -> Poll<Self::Output> {
|
||||
self.fut().poll(lw)
|
||||
}
|
||||
}
|
||||
|
||||
/// A future returned by [`Channel::call`] that resolves to a server response.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct Call<'a, Req, Resp> {
|
||||
fut: AndThenIdent<Send<'a, Req, Resp>, DispatchResponse<Resp>>,
|
||||
}
|
||||
|
||||
impl<'a, Req, Resp> Call<'a, Req, Resp> {
|
||||
unsafe_pinned!(fut: AndThenIdent<Send<'a, Req, Resp>, DispatchResponse<Resp>>);
|
||||
}
|
||||
|
||||
impl<'a, Req, Resp> Future for Call<'a, Req, Resp> {
|
||||
type Output = io::Result<Resp>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, lw: &LocalWaker) -> Poll<Self::Output> {
|
||||
self.fut().poll(lw)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp> Channel<Req, Resp> {
|
||||
/// Sends a request to the dispatch task to forward to the server, returning a [`Future`] that
|
||||
/// resolves when the request is sent (not when the response is received).
|
||||
fn send<'a>(&'a mut self, mut ctx: context::Context, request: Req) -> Send<'a, Req, Resp> {
|
||||
// Convert the context to the call context.
|
||||
ctx.trace_context.parent_id = Some(ctx.trace_context.span_id);
|
||||
ctx.trace_context.span_id = SpanId::random(&mut rand::thread_rng());
|
||||
|
||||
let timeout = ctx.deadline.as_duration();
|
||||
let deadline = Instant::now() + timeout;
|
||||
trace!(
|
||||
"[{}/{}] Queuing request with deadline {} (timeout {:?}).",
|
||||
ctx.trace_id(),
|
||||
self.server_addr,
|
||||
format_rfc3339(ctx.deadline),
|
||||
timeout,
|
||||
);
|
||||
|
||||
let (response_completion, response) = oneshot::channel();
|
||||
let cancellation = self.cancellation.clone();
|
||||
let request_id = self.next_request_id.fetch_add(1, Ordering::Relaxed);
|
||||
let server_addr = self.server_addr;
|
||||
Send {
|
||||
fut: MapOkDispatchResponse::new(
|
||||
MapErrConnectionReset::new(self.to_dispatch.send(DispatchRequest {
|
||||
ctx,
|
||||
request_id,
|
||||
request,
|
||||
response_completion,
|
||||
})),
|
||||
DispatchResponse {
|
||||
response: deadline_compat::Deadline::new(response, deadline),
|
||||
complete: false,
|
||||
request_id,
|
||||
cancellation,
|
||||
ctx,
|
||||
server_addr,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends a request to the dispatch task to forward to the server, returning a [`Future`] that
|
||||
/// resolves to the response.
|
||||
pub fn call<'a>(&'a mut self, context: context::Context, request: Req) -> Call<'a, Req, Resp> {
|
||||
Call {
|
||||
fut: AndThenIdent::new(self.send(context, request)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A server response that is completed by request dispatch when the corresponding response
|
||||
/// arrives off the wire.
|
||||
#[derive(Debug)]
|
||||
struct DispatchResponse<Resp> {
|
||||
response: deadline_compat::Deadline<oneshot::Receiver<Response<Resp>>>,
|
||||
ctx: context::Context,
|
||||
complete: bool,
|
||||
cancellation: RequestCancellation,
|
||||
request_id: u64,
|
||||
server_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl<Resp> DispatchResponse<Resp> {
|
||||
unsafe_pinned!(server_addr: SocketAddr);
|
||||
unsafe_pinned!(ctx: context::Context);
|
||||
}
|
||||
|
||||
impl<Resp> Future for DispatchResponse<Resp> {
|
||||
type Output = io::Result<Resp>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<io::Result<Resp>> {
|
||||
let resp = ready!(self.response.poll_unpin(waker));
|
||||
|
||||
self.complete = true;
|
||||
|
||||
Poll::Ready(match resp {
|
||||
Ok(resp) => Ok(resp.message?),
|
||||
Err(e) => Err({
|
||||
let trace_id = *self.ctx().trace_id();
|
||||
let server_addr = *self.server_addr();
|
||||
|
||||
if e.is_elapsed() {
|
||||
io::Error::new(
|
||||
io::ErrorKind::TimedOut,
|
||||
"Client dropped expired request.".to_string(),
|
||||
)
|
||||
} else if e.is_timer() {
|
||||
let e = e.into_timer().unwrap();
|
||||
if e.is_at_capacity() {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Cancelling request because an expiration could not be set \
|
||||
due to the timer being at capacity."
|
||||
.to_string(),
|
||||
)
|
||||
} else if e.is_shutdown() {
|
||||
panic!("[{}/{}] Timer was shutdown", trace_id, server_addr)
|
||||
} else {
|
||||
panic!(
|
||||
"[{}/{}] Unrecognized timer error: {}",
|
||||
trace_id, server_addr, e
|
||||
)
|
||||
}
|
||||
} else if e.is_inner() {
|
||||
// The oneshot is Canceled when the dispatch task ends.
|
||||
io::Error::from(io::ErrorKind::ConnectionReset)
|
||||
} else {
|
||||
panic!(
|
||||
"[{}/{}] Unrecognized deadline error: {}",
|
||||
trace_id, server_addr, e
|
||||
)
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Cancels the request when dropped, if not already complete.
|
||||
impl<Resp> Drop for DispatchResponse<Resp> {
|
||||
fn drop(&mut self) {
|
||||
if !self.complete {
|
||||
// The receiver needs to be closed to handle the edge case that the request has not
|
||||
// yet been received by the dispatch task. It is possible for the cancel message to
|
||||
// arrive before the request itself, in which case the request could get stuck in the
|
||||
// dispatch map forever if the server never responds (e.g. if the server dies while
|
||||
// responding). Even if the server does respond, it will have unnecessarily done work
|
||||
// for a client no longer waiting for a response. To avoid this, the dispatch task
|
||||
// checks if the receiver is closed before inserting the request in the map. By
|
||||
// closing the receiver before sending the cancel message, it is guaranteed that if the
|
||||
// dispatch task misses an early-arriving cancellation message, then it will see the
|
||||
// receiver as closed.
|
||||
self.response.get_mut().close();
|
||||
self.cancellation.cancel(self.request_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns a dispatch task on the default executor that manages the lifecycle of requests initiated
|
||||
/// by the returned [`Channel`].
|
||||
pub async fn spawn<Req, Resp, C>(
|
||||
config: Config,
|
||||
transport: C,
|
||||
server_addr: SocketAddr,
|
||||
) -> io::Result<Channel<Req, Resp>>
|
||||
where
|
||||
Req: marker::Send + 'static,
|
||||
Resp: marker::Send + 'static,
|
||||
C: Transport<Item = Response<Resp>, SinkItem = ClientMessage<Req>> + marker::Send + 'static,
|
||||
{
|
||||
let (to_dispatch, pending_requests) = mpsc::channel(config.pending_request_buffer);
|
||||
let (cancellation, canceled_requests) = cancellations();
|
||||
|
||||
crate::spawn(
|
||||
RequestDispatch {
|
||||
config,
|
||||
server_addr,
|
||||
canceled_requests,
|
||||
transport: transport.fuse(),
|
||||
in_flight_requests: FnvHashMap::default(),
|
||||
pending_requests: pending_requests.fuse(),
|
||||
}
|
||||
.unwrap_or_else(move |e| error!("[{}] Connection broken: {}", server_addr, e)),
|
||||
)
|
||||
.map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"Could not spawn client dispatch task. Is shutdown: {}",
|
||||
e.is_shutdown()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Channel {
|
||||
to_dispatch,
|
||||
cancellation,
|
||||
server_addr,
|
||||
next_request_id: Arc::new(AtomicU64::new(0)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Handles the lifecycle of requests, writing requests to the wire, managing cancellations,
|
||||
/// and dispatching responses to the appropriate channel.
|
||||
struct RequestDispatch<Req, Resp, C> {
|
||||
/// Writes requests to the wire and reads responses off the wire.
|
||||
transport: Fuse<C>,
|
||||
/// Requests waiting to be written to the wire.
|
||||
pending_requests: Fuse<mpsc::Receiver<DispatchRequest<Req, Resp>>>,
|
||||
/// Requests that were dropped.
|
||||
canceled_requests: CanceledRequests,
|
||||
/// Requests already written to the wire that haven't yet received responses.
|
||||
in_flight_requests: FnvHashMap<u64, InFlightData<Resp>>,
|
||||
/// Configures limits to prevent unlimited resource usage.
|
||||
config: Config,
|
||||
/// The address of the server connected to.
|
||||
server_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl<Req, Resp, C> RequestDispatch<Req, Resp, C>
|
||||
where
|
||||
Req: marker::Send,
|
||||
Resp: marker::Send,
|
||||
C: Transport<Item = Response<Resp>, SinkItem = ClientMessage<Req>>,
|
||||
{
|
||||
unsafe_pinned!(server_addr: SocketAddr);
|
||||
unsafe_pinned!(in_flight_requests: FnvHashMap<u64, InFlightData<Resp>>);
|
||||
unsafe_pinned!(canceled_requests: CanceledRequests);
|
||||
unsafe_pinned!(pending_requests: Fuse<mpsc::Receiver<DispatchRequest<Req, Resp>>>);
|
||||
unsafe_pinned!(transport: Fuse<C>);
|
||||
|
||||
fn pump_read(self: &mut Pin<&mut Self>, waker: &LocalWaker) -> Poll<Option<io::Result<()>>> {
|
||||
Poll::Ready(match ready!(self.transport().poll_next(waker)?) {
|
||||
Some(response) => {
|
||||
self.complete(response);
|
||||
Some(Ok(()))
|
||||
}
|
||||
None => {
|
||||
trace!("[{}] read half closed", self.server_addr());
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn pump_write(self: &mut Pin<&mut Self>, waker: &LocalWaker) -> Poll<Option<io::Result<()>>> {
|
||||
enum ReceiverStatus {
|
||||
NotReady,
|
||||
Closed,
|
||||
}
|
||||
|
||||
let pending_requests_status = match self.poll_next_request(waker)? {
|
||||
Poll::Ready(Some(dispatch_request)) => {
|
||||
self.write_request(dispatch_request)?;
|
||||
return Poll::Ready(Some(Ok(())));
|
||||
}
|
||||
Poll::Ready(None) => ReceiverStatus::Closed,
|
||||
Poll::Pending => ReceiverStatus::NotReady,
|
||||
};
|
||||
|
||||
let canceled_requests_status = match self.poll_next_cancellation(waker)? {
|
||||
Poll::Ready(Some((context, request_id))) => {
|
||||
self.write_cancel(context, request_id)?;
|
||||
return Poll::Ready(Some(Ok(())));
|
||||
}
|
||||
Poll::Ready(None) => ReceiverStatus::Closed,
|
||||
Poll::Pending => ReceiverStatus::NotReady,
|
||||
};
|
||||
|
||||
match (pending_requests_status, canceled_requests_status) {
|
||||
(ReceiverStatus::Closed, ReceiverStatus::Closed) => {
|
||||
ready!(self.transport().poll_flush(waker)?);
|
||||
Poll::Ready(None)
|
||||
}
|
||||
(ReceiverStatus::NotReady, _) | (_, ReceiverStatus::NotReady) => {
|
||||
// No more messages to process, so flush any messages buffered in the transport.
|
||||
ready!(self.transport().poll_flush(waker)?);
|
||||
|
||||
// Even if we fully-flush, we return Pending, because we have no more requests
|
||||
// or cancellations right now.
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Yields the next pending request, if one is ready to be sent.
|
||||
fn poll_next_request(
|
||||
self: &mut Pin<&mut Self>,
|
||||
waker: &LocalWaker,
|
||||
) -> Poll<Option<io::Result<DispatchRequest<Req, Resp>>>> {
|
||||
if self.in_flight_requests().len() >= self.config.max_in_flight_requests {
|
||||
info!(
|
||||
"At in-flight request capacity ({}/{}).",
|
||||
self.in_flight_requests().len(),
|
||||
self.config.max_in_flight_requests
|
||||
);
|
||||
|
||||
// No need to schedule a wakeup, because timers and responses are responsible
|
||||
// for clearing out in-flight requests.
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
while let Poll::Pending = self.transport().poll_ready(waker)? {
|
||||
// We can't yield a request-to-be-sent before the transport is capable of buffering it.
|
||||
ready!(self.transport().poll_flush(waker)?);
|
||||
}
|
||||
|
||||
loop {
|
||||
match ready!(self.pending_requests().poll_next_unpin(waker)) {
|
||||
Some(request) => {
|
||||
if request.response_completion.is_canceled() {
|
||||
trace!(
|
||||
"[{}] Request canceled before being sent.",
|
||||
request.ctx.trace_id()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Poll::Ready(Some(Ok(request)));
|
||||
}
|
||||
None => {
|
||||
trace!("[{}] pending_requests closed", self.server_addr());
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Yields the next pending cancellation, and, if one is ready, cancels the associated request.
|
||||
fn poll_next_cancellation(
|
||||
self: &mut Pin<&mut Self>,
|
||||
waker: &LocalWaker,
|
||||
) -> Poll<Option<io::Result<(context::Context, u64)>>> {
|
||||
while let Poll::Pending = self.transport().poll_ready(waker)? {
|
||||
ready!(self.transport().poll_flush(waker)?);
|
||||
}
|
||||
|
||||
loop {
|
||||
match ready!(self.canceled_requests().poll_next_unpin(waker)) {
|
||||
Some(request_id) => {
|
||||
if let Some(in_flight_data) = self.in_flight_requests().remove(&request_id) {
|
||||
self.in_flight_requests().compact(0.1);
|
||||
|
||||
debug!(
|
||||
"[{}/{}] Removed request.",
|
||||
in_flight_data.ctx.trace_id(),
|
||||
self.server_addr()
|
||||
);
|
||||
|
||||
return Poll::Ready(Some(Ok((in_flight_data.ctx, request_id))));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
trace!("[{}] canceled_requests closed.", self.server_addr());
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_request(
|
||||
self: &mut Pin<&mut Self>,
|
||||
dispatch_request: DispatchRequest<Req, Resp>,
|
||||
) -> io::Result<()> {
|
||||
let request_id = dispatch_request.request_id;
|
||||
let request = ClientMessage {
|
||||
trace_context: dispatch_request.ctx.trace_context,
|
||||
message: ClientMessageKind::Request(Request {
|
||||
id: request_id,
|
||||
message: dispatch_request.request,
|
||||
deadline: dispatch_request.ctx.deadline,
|
||||
}),
|
||||
};
|
||||
self.transport().start_send(request)?;
|
||||
self.in_flight_requests().insert(
|
||||
request_id,
|
||||
InFlightData {
|
||||
ctx: dispatch_request.ctx,
|
||||
response_completion: dispatch_request.response_completion,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_cancel(
|
||||
self: &mut Pin<&mut Self>,
|
||||
context: context::Context,
|
||||
request_id: u64,
|
||||
) -> io::Result<()> {
|
||||
let trace_id = *context.trace_id();
|
||||
let cancel = ClientMessage {
|
||||
trace_context: context.trace_context,
|
||||
message: ClientMessageKind::Cancel { request_id },
|
||||
};
|
||||
self.transport().start_send(cancel)?;
|
||||
trace!("[{}/{}] Cancel message sent.", trace_id, self.server_addr());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// Sends a server response to the client task that initiated the associated request.
|
||||
fn complete(self: &mut Pin<&mut Self>, response: Response<Resp>) -> bool {
|
||||
if let Some(in_flight_data) = self.in_flight_requests().remove(&response.request_id) {
|
||||
self.in_flight_requests().compact(0.1);
|
||||
|
||||
trace!(
|
||||
"[{}/{}] Received response.",
|
||||
in_flight_data.ctx.trace_id(),
|
||||
self.server_addr()
|
||||
);
|
||||
let _ = in_flight_data.response_completion.send(response);
|
||||
return true;
|
||||
}
|
||||
|
||||
debug!(
|
||||
"[{}] No in-flight request found for request_id = {}.",
|
||||
self.server_addr(),
|
||||
response.request_id
|
||||
);
|
||||
|
||||
// If the response completion was absent, then the request was already canceled.
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, C> Future for RequestDispatch<Req, Resp, C>
|
||||
where
|
||||
Req: marker::Send,
|
||||
Resp: marker::Send,
|
||||
C: Transport<Item = Response<Resp>, SinkItem = ClientMessage<Req>>,
|
||||
{
|
||||
type Output = io::Result<()>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<io::Result<()>> {
|
||||
trace!("[{}] RequestDispatch::poll", self.server_addr());
|
||||
loop {
|
||||
match (self.pump_read(waker)?, self.pump_write(waker)?) {
|
||||
(read, write @ Poll::Ready(None)) => {
|
||||
if self.in_flight_requests().is_empty() {
|
||||
info!(
|
||||
"[{}] Shutdown: write half closed, and no requests in flight.",
|
||||
self.server_addr()
|
||||
);
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
match read {
|
||||
Poll::Ready(Some(())) => continue,
|
||||
_ => {
|
||||
trace!(
|
||||
"[{}] read: {:?}, write: {:?}, (not ready)",
|
||||
self.server_addr(),
|
||||
read,
|
||||
write,
|
||||
);
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
}
|
||||
(read @ Poll::Ready(Some(())), write) | (read, write @ Poll::Ready(Some(()))) => {
|
||||
trace!(
|
||||
"[{}] read: {:?}, write: {:?}",
|
||||
self.server_addr(),
|
||||
read,
|
||||
write,
|
||||
)
|
||||
}
|
||||
(read, write) => {
|
||||
trace!(
|
||||
"[{}] read: {:?}, write: {:?} (not ready)",
|
||||
self.server_addr(),
|
||||
read,
|
||||
write,
|
||||
);
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A server-bound request sent from a [`Channel`] to request dispatch, which will then manage
|
||||
/// the lifecycle of the request.
|
||||
#[derive(Debug)]
|
||||
struct DispatchRequest<Req, Resp> {
|
||||
ctx: context::Context,
|
||||
request_id: u64,
|
||||
request: Req,
|
||||
response_completion: oneshot::Sender<Response<Resp>>,
|
||||
}
|
||||
|
||||
struct InFlightData<Resp> {
|
||||
ctx: context::Context,
|
||||
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. Additionally, each request has a clone
|
||||
// of the sender, so the bounded channel would have the same behavior,
|
||||
// since it guarantees a slot.
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
(RequestCancellation(tx), CanceledRequests(rx))
|
||||
}
|
||||
|
||||
impl RequestCancellation {
|
||||
/// Cancels the request with ID `request_id`.
|
||||
fn cancel(&mut self, request_id: u64) {
|
||||
let _ = self.0.unbounded_send(request_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for CanceledRequests {
|
||||
type Item = u64;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Option<u64>> {
|
||||
self.0.poll_next_unpin(waker)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
struct MapErrConnectionReset<Fut> {
|
||||
future: Fut,
|
||||
finished: Option<()>,
|
||||
}
|
||||
|
||||
impl<Fut> MapErrConnectionReset<Fut> {
|
||||
unsafe_pinned!(future: Fut);
|
||||
unsafe_unpinned!(finished: Option<()>);
|
||||
|
||||
fn new(future: Fut) -> MapErrConnectionReset<Fut> {
|
||||
MapErrConnectionReset {
|
||||
future,
|
||||
finished: Some(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut: Unpin> Unpin for MapErrConnectionReset<Fut> {}
|
||||
|
||||
impl<Fut> Future for MapErrConnectionReset<Fut>
|
||||
where
|
||||
Fut: TryFuture,
|
||||
{
|
||||
type Output = io::Result<Fut::Ok>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, lw: &LocalWaker) -> Poll<Self::Output> {
|
||||
match self.future().try_poll(lw) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(result) => {
|
||||
self.finished().take().expect(
|
||||
"MapErrConnectionReset must not be polled after it returned `Poll::Ready`",
|
||||
);
|
||||
Poll::Ready(result.map_err(|_| io::Error::from(io::ErrorKind::ConnectionReset)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
struct MapOkDispatchResponse<Fut, Resp> {
|
||||
future: Fut,
|
||||
response: Option<DispatchResponse<Resp>>,
|
||||
}
|
||||
|
||||
impl<Fut, Resp> MapOkDispatchResponse<Fut, Resp> {
|
||||
unsafe_pinned!(future: Fut);
|
||||
unsafe_unpinned!(response: Option<DispatchResponse<Resp>>);
|
||||
|
||||
fn new(future: Fut, response: DispatchResponse<Resp>) -> MapOkDispatchResponse<Fut, Resp> {
|
||||
MapOkDispatchResponse {
|
||||
future,
|
||||
response: Some(response),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut: Unpin, Resp> Unpin for MapOkDispatchResponse<Fut, Resp> {}
|
||||
|
||||
impl<Fut, Resp> Future for MapOkDispatchResponse<Fut, Resp>
|
||||
where
|
||||
Fut: TryFuture,
|
||||
{
|
||||
type Output = Result<DispatchResponse<Resp>, Fut::Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, lw: &LocalWaker) -> Poll<Self::Output> {
|
||||
match self.future().try_poll(lw) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(result) => {
|
||||
let response = self
|
||||
.response()
|
||||
.take()
|
||||
.expect("MapOk must not be polled after it returned `Poll::Ready`");
|
||||
Poll::Ready(result.map(|_| response))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
struct AndThenIdent<Fut1, Fut2> {
|
||||
try_chain: TryChain<Fut1, Fut2>,
|
||||
}
|
||||
|
||||
impl<Fut1, Fut2> AndThenIdent<Fut1, Fut2>
|
||||
where
|
||||
Fut1: TryFuture<Ok = Fut2>,
|
||||
Fut2: TryFuture,
|
||||
{
|
||||
unsafe_pinned!(try_chain: TryChain<Fut1, Fut2>);
|
||||
|
||||
/// Creates a new `Then`.
|
||||
fn new(future: Fut1) -> AndThenIdent<Fut1, Fut2> {
|
||||
AndThenIdent {
|
||||
try_chain: TryChain::new(future),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut1, Fut2> Future for AndThenIdent<Fut1, Fut2>
|
||||
where
|
||||
Fut1: TryFuture<Ok = Fut2>,
|
||||
Fut2: TryFuture<Error = Fut1::Error>,
|
||||
{
|
||||
type Output = Result<Fut2::Ok, Fut2::Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, lw: &LocalWaker) -> Poll<Self::Output> {
|
||||
self.try_chain().poll(lw, |result| match result {
|
||||
Ok(ok) => TryChainAction::Future(ok),
|
||||
Err(err) => TryChainAction::Output(Err(err)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
#[derive(Debug)]
|
||||
enum TryChain<Fut1, Fut2> {
|
||||
First(Fut1),
|
||||
Second(Fut2),
|
||||
Empty,
|
||||
}
|
||||
|
||||
enum TryChainAction<Fut2>
|
||||
where
|
||||
Fut2: TryFuture,
|
||||
{
|
||||
Future(Fut2),
|
||||
Output(Result<Fut2::Ok, Fut2::Error>),
|
||||
}
|
||||
|
||||
impl<Fut1, Fut2> TryChain<Fut1, Fut2>
|
||||
where
|
||||
Fut1: TryFuture<Ok = Fut2>,
|
||||
Fut2: TryFuture,
|
||||
{
|
||||
fn new(fut1: Fut1) -> TryChain<Fut1, Fut2> {
|
||||
TryChain::First(fut1)
|
||||
}
|
||||
|
||||
fn poll<F>(self: Pin<&mut Self>, lw: &LocalWaker, f: F) -> Poll<Result<Fut2::Ok, Fut2::Error>>
|
||||
where
|
||||
F: FnOnce(Result<Fut1::Ok, Fut1::Error>) -> TryChainAction<Fut2>,
|
||||
{
|
||||
let mut f = Some(f);
|
||||
|
||||
// Safe to call `get_mut_unchecked` because we won't move the futures.
|
||||
let this = unsafe { Pin::get_mut_unchecked(self) };
|
||||
|
||||
loop {
|
||||
let output = match this {
|
||||
TryChain::First(fut1) => {
|
||||
// Poll the first future
|
||||
match unsafe { Pin::new_unchecked(fut1) }.try_poll(lw) {
|
||||
Poll::Pending => return Poll::Pending,
|
||||
Poll::Ready(output) => output,
|
||||
}
|
||||
}
|
||||
TryChain::Second(fut2) => {
|
||||
// Poll the second future
|
||||
return unsafe { Pin::new_unchecked(fut2) }.try_poll(lw);
|
||||
}
|
||||
TryChain::Empty => {
|
||||
panic!("future must not be polled after it returned `Poll::Ready`");
|
||||
}
|
||||
};
|
||||
|
||||
*this = TryChain::Empty; // Drop fut1
|
||||
let f = f.take().unwrap();
|
||||
match f(output) {
|
||||
TryChainAction::Future(fut2) => *this = TryChain::Second(fut2),
|
||||
TryChainAction::Output(output) => return Poll::Ready(output),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{CanceledRequests, Channel, RequestCancellation, RequestDispatch};
|
||||
use crate::{
|
||||
client::Config,
|
||||
context,
|
||||
transport::{self, channel::UnboundedChannel},
|
||||
ClientMessage, Response,
|
||||
};
|
||||
use fnv::FnvHashMap;
|
||||
use futures::{channel::mpsc, prelude::*, Poll};
|
||||
use futures_test::task::noop_local_waker_ref;
|
||||
use std::{
|
||||
marker,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
pin::Pin,
|
||||
sync::atomic::AtomicU64,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn stage_request() {
|
||||
let (mut dispatch, mut channel, _server_channel) = set_up();
|
||||
|
||||
// Test that a request future dropped before it's processed by dispatch will cause the request
|
||||
// to not be added to the in-flight request map.
|
||||
let _resp = tokio::runtime::current_thread::block_on_all(
|
||||
channel
|
||||
.send(context::current(), "hi".to_string())
|
||||
.boxed()
|
||||
.compat(),
|
||||
);
|
||||
|
||||
let mut dispatch = Pin::new(&mut dispatch);
|
||||
let waker = &noop_local_waker_ref();
|
||||
|
||||
let req = dispatch.poll_next_request(waker).ready();
|
||||
assert!(req.is_some());
|
||||
|
||||
let req = req.unwrap();
|
||||
assert_eq!(req.request_id, 0);
|
||||
assert_eq!(req.request, "hi".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stage_request_response_future_dropped() {
|
||||
let (mut dispatch, mut channel, _server_channel) = set_up();
|
||||
|
||||
// Test that a request future dropped before it's processed by dispatch will cause the request
|
||||
// to not be added to the in-flight request map.
|
||||
let resp = tokio::runtime::current_thread::block_on_all(
|
||||
channel
|
||||
.send(context::current(), "hi".into())
|
||||
.boxed()
|
||||
.compat(),
|
||||
)
|
||||
.unwrap();
|
||||
drop(resp);
|
||||
drop(channel);
|
||||
|
||||
let mut dispatch = Pin::new(&mut dispatch);
|
||||
let waker = &noop_local_waker_ref();
|
||||
|
||||
dispatch.poll_next_cancellation(waker).unwrap();
|
||||
assert!(dispatch.poll_next_request(waker).ready().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stage_request_response_future_closed() {
|
||||
let (mut dispatch, mut channel, _server_channel) = set_up();
|
||||
|
||||
// Test that a request future that's closed its receiver but not yet canceled its request --
|
||||
// i.e. still in `drop fn` -- will cause the request to not be added to the in-flight request
|
||||
// map.
|
||||
let resp = tokio::runtime::current_thread::block_on_all(
|
||||
channel
|
||||
.send(context::current(), "hi".into())
|
||||
.boxed()
|
||||
.compat(),
|
||||
)
|
||||
.unwrap();
|
||||
drop(resp);
|
||||
drop(channel);
|
||||
|
||||
let mut dispatch = Pin::new(&mut dispatch);
|
||||
let waker = &noop_local_waker_ref();
|
||||
assert!(dispatch.poll_next_request(waker).ready().is_none());
|
||||
}
|
||||
|
||||
fn set_up() -> (
|
||||
RequestDispatch<String, String, UnboundedChannel<Response<String>, ClientMessage<String>>>,
|
||||
Channel<String, String>,
|
||||
UnboundedChannel<ClientMessage<String>, Response<String>>,
|
||||
) {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let (to_dispatch, pending_requests) = mpsc::channel(1);
|
||||
let (cancel_tx, canceled_requests) = mpsc::unbounded();
|
||||
let (client_channel, server_channel) = transport::channel::unbounded();
|
||||
|
||||
let dispatch = RequestDispatch::<String, String, _> {
|
||||
transport: client_channel.fuse(),
|
||||
pending_requests: pending_requests.fuse(),
|
||||
canceled_requests: CanceledRequests(canceled_requests),
|
||||
in_flight_requests: FnvHashMap::default(),
|
||||
config: Config::default(),
|
||||
server_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
|
||||
};
|
||||
|
||||
let cancellation = RequestCancellation(cancel_tx);
|
||||
let channel = Channel {
|
||||
to_dispatch,
|
||||
cancellation,
|
||||
next_request_id: Arc::new(AtomicU64::new(0)),
|
||||
server_addr: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0),
|
||||
};
|
||||
|
||||
(dispatch, channel, server_channel)
|
||||
}
|
||||
|
||||
trait PollTest {
|
||||
type T;
|
||||
fn unwrap(self) -> Poll<Self::T>;
|
||||
fn ready(self) -> Self::T;
|
||||
}
|
||||
|
||||
impl<T, E> PollTest for Poll<Option<Result<T, E>>>
|
||||
where
|
||||
E: ::std::fmt::Display + marker::Send + 'static,
|
||||
{
|
||||
type T = Option<T>;
|
||||
|
||||
fn unwrap(self) -> Poll<Option<T>> {
|
||||
match self {
|
||||
Poll::Ready(Some(Ok(t))) => Poll::Ready(Some(t)),
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
Poll::Ready(Some(Err(e))) => panic!(e.to_string()),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
fn ready(self) -> Option<T> {
|
||||
match self {
|
||||
Poll::Ready(Some(Ok(t))) => Some(t),
|
||||
Poll::Ready(None) => None,
|
||||
Poll::Ready(Some(Err(e))) => panic!(e.to_string()),
|
||||
Poll::Pending => panic!("Pending"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
151
rpc/src/client/mod.rs
Normal file
151
rpc/src/client/mod.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! Provides a client that connects to a server and sends multiplexed requests.
|
||||
|
||||
use crate::{context, ClientMessage, Response, Transport};
|
||||
use futures::prelude::*;
|
||||
use log::warn;
|
||||
use std::{
|
||||
io,
|
||||
net::{Ipv4Addr, SocketAddr},
|
||||
};
|
||||
|
||||
/// Provides a [`Client`] backed by a transport.
|
||||
pub mod channel;
|
||||
pub use self::channel::Channel;
|
||||
|
||||
/// Sends multiplexed requests to, and receives responses from, a server.
|
||||
pub trait Client<'a, Req> {
|
||||
/// The response type.
|
||||
type Response;
|
||||
|
||||
/// The future response.
|
||||
type Future: Future<Output = io::Result<Self::Response>> + 'a;
|
||||
|
||||
/// Initiates a request, sending it to the dispatch task.
|
||||
///
|
||||
/// Returns a [`Future`] that resolves to this client and the future response
|
||||
/// once the request is successfully enqueued.
|
||||
///
|
||||
/// [`Future`]: futures::Future
|
||||
fn call(&'a mut self, ctx: context::Context, request: Req) -> Self::Future;
|
||||
|
||||
/// Returns a Client that applies a post-processing function to the returned response.
|
||||
fn map_response<F, R>(self, f: F) -> MapResponse<Self, F>
|
||||
where
|
||||
F: FnMut(Self::Response) -> R,
|
||||
Self: Sized,
|
||||
{
|
||||
MapResponse { inner: self, f }
|
||||
}
|
||||
|
||||
/// Returns a Client that applies a pre-processing function to the request.
|
||||
fn with_request<F, Req2>(self, f: F) -> WithRequest<Self, F>
|
||||
where
|
||||
F: FnMut(Req2) -> Req,
|
||||
Self: Sized,
|
||||
{
|
||||
WithRequest { inner: self, f }
|
||||
}
|
||||
}
|
||||
|
||||
/// A Client that applies a function to the returned response.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MapResponse<C, F> {
|
||||
inner: C,
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl<'a, C, F, Req, Resp, Resp2> Client<'a, Req> for MapResponse<C, F>
|
||||
where
|
||||
C: Client<'a, Req, Response = Resp>,
|
||||
F: FnMut(Resp) -> Resp2 + 'a,
|
||||
{
|
||||
type Response = Resp2;
|
||||
type Future = futures::future::MapOk<<C as Client<'a, Req>>::Future, &'a mut F>;
|
||||
|
||||
fn call(&'a mut self, ctx: context::Context, request: Req) -> Self::Future {
|
||||
self.inner.call(ctx, request).map_ok(&mut self.f)
|
||||
}
|
||||
}
|
||||
|
||||
/// A Client that applies a pre-processing function to the request.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WithRequest<C, F> {
|
||||
inner: C,
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl<'a, C, F, Req, Req2, Resp> Client<'a, Req2> for WithRequest<C, F>
|
||||
where
|
||||
C: Client<'a, Req, Response = Resp>,
|
||||
F: FnMut(Req2) -> Req,
|
||||
{
|
||||
type Response = Resp;
|
||||
type Future = <C as Client<'a, Req>>::Future;
|
||||
|
||||
fn call(&'a mut self, ctx: context::Context, request: Req2) -> Self::Future {
|
||||
self.inner.call(ctx, (self.f)(request))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Req, Resp> Client<'a, Req> for Channel<Req, Resp>
|
||||
where
|
||||
Req: 'a,
|
||||
Resp: 'a,
|
||||
{
|
||||
type Response = Resp;
|
||||
type Future = channel::Call<'a, Req, Resp>;
|
||||
|
||||
fn call(&'a mut self, ctx: context::Context, request: Req) -> channel::Call<'a, Req, Resp> {
|
||||
self.call(ctx, request)
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings that control the behavior of the client.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Config {
|
||||
/// The number of requests that can be in flight at once.
|
||||
/// `max_in_flight_requests` controls the size of the map used by the client
|
||||
/// for storing pending requests.
|
||||
pub max_in_flight_requests: usize,
|
||||
/// The number of requests that can be buffered client-side before being sent.
|
||||
/// `pending_requests_buffer` controls the size of the channel clients use
|
||||
/// to communicate with the request dispatch task.
|
||||
pub pending_request_buffer: usize,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
max_in_flight_requests: 1_000,
|
||||
pending_request_buffer: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new Client by wrapping a [`Transport`] and spawning a dispatch task
|
||||
/// that manages the lifecycle of requests.
|
||||
///
|
||||
/// Must only be called from on an executor.
|
||||
pub async fn new<Req, Resp, T>(config: Config, transport: T) -> io::Result<Channel<Req, Resp>>
|
||||
where
|
||||
Req: Send + 'static,
|
||||
Resp: Send + 'static,
|
||||
T: Transport<Item = Response<Resp>, SinkItem = ClientMessage<Req>> + Send + 'static,
|
||||
{
|
||||
let server_addr = transport.peer_addr().unwrap_or_else(|e| {
|
||||
warn!(
|
||||
"Setting peer to unspecified because peer could not be determined: {}",
|
||||
e
|
||||
);
|
||||
SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0)
|
||||
});
|
||||
|
||||
Ok(await!(channel::spawn(config, transport, server_addr))?)
|
||||
}
|
||||
45
rpc/src/context.rs
Normal file
45
rpc/src/context.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! Provides a request context that carries a deadline and trace context. This context is sent from
|
||||
//! client to server and is used by the server to enforce response deadlines.
|
||||
|
||||
use std::time::{Duration, SystemTime};
|
||||
use trace::{self, TraceId};
|
||||
|
||||
/// A request context that carries request-scoped information like deadlines and trace information.
|
||||
/// It is sent from client to server and is used by the server to enforce response deadlines.
|
||||
///
|
||||
/// The context should not be stored directly in a server implementation, because the context will
|
||||
/// be different for each request in scope.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[non_exhaustive]
|
||||
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.
|
||||
pub deadline: SystemTime,
|
||||
/// Uniquely identifies requests originating from the same source.
|
||||
/// When a service handles a request by making requests itself, those requests should
|
||||
/// include the same `trace_id` as that included on the original request. This way,
|
||||
/// users can trace related actions across a distributed system.
|
||||
pub trace_context: trace::Context,
|
||||
}
|
||||
|
||||
/// Returns the context for the current request, or a default Context if no request is active.
|
||||
// TODO: populate Context with request-scoped data, with default fallbacks.
|
||||
pub fn current() -> Context {
|
||||
Context {
|
||||
deadline: SystemTime::now() + Duration::from_secs(10),
|
||||
trace_context: trace::Context::new_root(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Returns the ID of the request-scoped trace.
|
||||
pub fn trace_id(&self) -> &TraceId {
|
||||
&self.trace_context.trace_id
|
||||
}
|
||||
}
|
||||
209
rpc/src/lib.rs
Normal file
209
rpc/src/lib.rs
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
#![feature(
|
||||
non_exhaustive,
|
||||
integer_atomics,
|
||||
try_trait,
|
||||
futures_api,
|
||||
pin,
|
||||
arbitrary_self_types,
|
||||
await_macro,
|
||||
async_await,
|
||||
)]
|
||||
#![deny(missing_docs, missing_debug_implementations)]
|
||||
|
||||
//! An RPC framework providing client and server.
|
||||
//!
|
||||
//! Features:
|
||||
//! * RPC deadlines, both client- and server-side.
|
||||
//! * Cascading cancellation (works with multiple hops).
|
||||
//! * Configurable limits
|
||||
//! * In-flight requests, both client and server-side.
|
||||
//! * Server-side limit is per-connection.
|
||||
//! * When the server reaches the in-flight request maximum, it returns a throttled error
|
||||
//! to the client.
|
||||
//! * When the client reaches the in-flight request max, messages are buffered up to a
|
||||
//! configurable maximum, beyond which the requests are back-pressured.
|
||||
//! * Server connections.
|
||||
//! * Total and per-IP limits.
|
||||
//! * When an incoming connection is accepted, if already at maximum, the connection is
|
||||
//! dropped.
|
||||
//! * Transport agnostic.
|
||||
|
||||
pub mod client;
|
||||
pub mod context;
|
||||
pub mod server;
|
||||
pub mod transport;
|
||||
pub(crate) mod util;
|
||||
|
||||
pub use crate::{client::Client, server::Server, transport::Transport};
|
||||
|
||||
use futures::{
|
||||
task::{Spawn, SpawnError, SpawnExt},
|
||||
Future,
|
||||
};
|
||||
use std::{cell::RefCell, io, sync::Once, time::SystemTime};
|
||||
|
||||
/// A message from a client to a server.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(
|
||||
feature = "serde1",
|
||||
derive(serde::Serialize, serde::Deserialize)
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub struct ClientMessage<T> {
|
||||
/// The trace context associates the message with a specific chain of causally-related actions,
|
||||
/// possibly orchestrated across many distributed systems.
|
||||
pub trace_context: trace::Context,
|
||||
/// The message payload.
|
||||
pub message: ClientMessageKind<T>,
|
||||
}
|
||||
|
||||
/// Different messages that can be sent from a client to a server.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(
|
||||
feature = "serde1",
|
||||
derive(serde::Serialize, serde::Deserialize)
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub enum ClientMessageKind<T> {
|
||||
/// A request initiated by a user. The server responds to a request by invoking a
|
||||
/// service-provided request handler. The handler completes with a [`response`](Response), which
|
||||
/// the server sends back to the client.
|
||||
Request(Request<T>),
|
||||
/// A command to cancel an in-flight request, automatically sent by the client when a response
|
||||
/// future is dropped.
|
||||
///
|
||||
/// When received, the server will immediately cancel the main task (top-level future) of the
|
||||
/// request handler for the associated request. Any tasks spawned by the request handler will
|
||||
/// not be canceled, because the framework layer does not
|
||||
/// know about them.
|
||||
Cancel {
|
||||
/// The ID of the request to cancel.
|
||||
request_id: u64,
|
||||
},
|
||||
}
|
||||
|
||||
/// A request from a client to a server.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(
|
||||
feature = "serde1",
|
||||
derive(serde::Serialize, serde::Deserialize)
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub struct Request<T> {
|
||||
/// Uniquely identifies the request across all requests sent over a single channel.
|
||||
pub id: u64,
|
||||
/// The request body.
|
||||
pub message: T,
|
||||
/// When the client expects the request to be complete by. The server will cancel the request
|
||||
/// if it is not complete by this time.
|
||||
#[cfg_attr(
|
||||
feature = "serde1",
|
||||
serde(serialize_with = "util::serde::serialize_epoch_secs")
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "serde1",
|
||||
serde(deserialize_with = "util::serde::deserialize_epoch_secs")
|
||||
)]
|
||||
pub deadline: SystemTime,
|
||||
}
|
||||
|
||||
/// A response from a server to a client.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "serde1",
|
||||
derive(serde::Serialize, serde::Deserialize)
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub struct Response<T> {
|
||||
/// The ID of the request being responded to.
|
||||
pub request_id: u64,
|
||||
/// The response body, or an error if the request failed.
|
||||
pub message: Result<T, ServerError>,
|
||||
}
|
||||
|
||||
/// An error response from a server to a client.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(
|
||||
feature = "serde1",
|
||||
derive(serde::Serialize, serde::Deserialize)
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub struct ServerError {
|
||||
#[cfg_attr(
|
||||
feature = "serde1",
|
||||
serde(serialize_with = "util::serde::serialize_io_error_kind_as_u32")
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "serde1",
|
||||
serde(deserialize_with = "util::serde::deserialize_io_error_kind_from_u32")
|
||||
)]
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
impl From<ServerError> for io::Error {
|
||||
fn from(e: ServerError) -> io::Error {
|
||||
io::Error::new(e.kind, e.detail.unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Request<T> {
|
||||
/// Returns the deadline for this request.
|
||||
pub fn deadline(&self) -> &SystemTime {
|
||||
&self.deadline
|
||||
}
|
||||
}
|
||||
|
||||
static INIT: Once = Once::new();
|
||||
static mut SEED_SPAWN: Option<Box<dyn CloneSpawn>> = None;
|
||||
thread_local! {
|
||||
static SPAWN: RefCell<Box<dyn CloneSpawn>> = {
|
||||
unsafe {
|
||||
// INIT must always be called before accessing SPAWN.
|
||||
// Otherwise, accessing SPAWN can trigger undefined behavior due to race conditions.
|
||||
INIT.call_once(|| {});
|
||||
RefCell::new(SEED_SPAWN.clone().expect("init() must be called."))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Initializes the RPC library with a mechanism to spawn futures on the user's runtime.
|
||||
/// Client stubs and servers both use the initialized spawn.
|
||||
///
|
||||
/// Init only has an effect the first time it is called. If called previously, successive calls to
|
||||
/// init are noops.
|
||||
pub fn init(spawn: impl Spawn + Clone + 'static) {
|
||||
unsafe {
|
||||
INIT.call_once(|| {
|
||||
SEED_SPAWN = Some(Box::new(spawn));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn spawn(future: impl Future<Output = ()> + Send + 'static) -> Result<(), SpawnError> {
|
||||
SPAWN.with(|spawn| spawn.borrow_mut().spawn(future))
|
||||
}
|
||||
|
||||
trait CloneSpawn: Spawn {
|
||||
fn box_clone(&self) -> Box<dyn CloneSpawn>;
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn CloneSpawn> {
|
||||
fn clone(&self) -> Self {
|
||||
self.box_clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Spawn + Clone + 'static> CloneSpawn for S {
|
||||
fn box_clone(&self) -> Box<dyn CloneSpawn> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
260
rpc/src/server/filter.rs
Normal file
260
rpc/src/server/filter.rs
Normal file
@@ -0,0 +1,260 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use crate::{
|
||||
server::{Channel, Config},
|
||||
util::Compact,
|
||||
ClientMessage, Response, Transport,
|
||||
};
|
||||
use fnv::FnvHashMap;
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
prelude::*,
|
||||
ready,
|
||||
stream::Fuse,
|
||||
task::{LocalWaker, Poll},
|
||||
};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use pin_utils::unsafe_pinned;
|
||||
use std::{
|
||||
collections::hash_map::Entry,
|
||||
io,
|
||||
marker::PhantomData,
|
||||
net::{IpAddr, SocketAddr},
|
||||
ops::Try,
|
||||
option::NoneError,
|
||||
pin::Pin,
|
||||
};
|
||||
|
||||
/// Drops connections under configurable conditions:
|
||||
///
|
||||
/// 1. If the max number of connections is reached.
|
||||
/// 2. If the max number of connections for a single IP is reached.
|
||||
#[derive(Debug)]
|
||||
pub struct ConnectionFilter<S, Req, Resp> {
|
||||
listener: Fuse<S>,
|
||||
closed_connections: mpsc::UnboundedSender<SocketAddr>,
|
||||
closed_connections_rx: mpsc::UnboundedReceiver<SocketAddr>,
|
||||
config: Config,
|
||||
connections_per_ip: FnvHashMap<IpAddr, usize>,
|
||||
open_connections: usize,
|
||||
ghost: PhantomData<(Req, Resp)>,
|
||||
}
|
||||
|
||||
enum NewConnection<Req, Resp, C> {
|
||||
Filtered,
|
||||
Accepted(Channel<Req, Resp, C>),
|
||||
}
|
||||
|
||||
impl<Req, Resp, C> Try for NewConnection<Req, Resp, C> {
|
||||
type Ok = Channel<Req, Resp, C>;
|
||||
type Error = NoneError;
|
||||
|
||||
fn into_result(self) -> Result<Channel<Req, Resp, C>, NoneError> {
|
||||
match self {
|
||||
NewConnection::Filtered => Err(NoneError),
|
||||
NewConnection::Accepted(channel) => Ok(channel),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_error(_: NoneError) -> Self {
|
||||
NewConnection::Filtered
|
||||
}
|
||||
|
||||
fn from_ok(channel: Channel<Req, Resp, C>) -> Self {
|
||||
NewConnection::Accepted(channel)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req, Resp> ConnectionFilter<S, Req, Resp> {
|
||||
unsafe_pinned!(open_connections: usize);
|
||||
unsafe_pinned!(config: Config);
|
||||
unsafe_pinned!(connections_per_ip: FnvHashMap<IpAddr, usize>);
|
||||
unsafe_pinned!(closed_connections_rx: mpsc::UnboundedReceiver<SocketAddr>);
|
||||
unsafe_pinned!(listener: Fuse<S>);
|
||||
|
||||
/// Sheds new connections to stay under configured limits.
|
||||
pub fn filter<C>(listener: S, config: Config) -> Self
|
||||
where
|
||||
S: Stream<Item = Result<C, io::Error>>,
|
||||
C: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
||||
{
|
||||
let (closed_connections, closed_connections_rx) = mpsc::unbounded();
|
||||
|
||||
ConnectionFilter {
|
||||
listener: listener.fuse(),
|
||||
closed_connections,
|
||||
closed_connections_rx,
|
||||
config,
|
||||
connections_per_ip: FnvHashMap::default(),
|
||||
open_connections: 0,
|
||||
ghost: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_new_connection<C>(self: &mut Pin<&mut Self>, stream: C) -> NewConnection<Req, Resp, C>
|
||||
where
|
||||
C: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
||||
{
|
||||
let peer = match stream.peer_addr() {
|
||||
Ok(peer) => peer,
|
||||
Err(e) => {
|
||||
warn!("Could not get peer_addr of new connection: {}", e);
|
||||
return NewConnection::Filtered;
|
||||
}
|
||||
};
|
||||
|
||||
let open_connections = *self.open_connections();
|
||||
if open_connections >= self.config().max_connections {
|
||||
warn!(
|
||||
"[{}] Shedding connection because the maximum open connections \
|
||||
limit is reached ({}/{}).",
|
||||
peer,
|
||||
open_connections,
|
||||
self.config().max_connections
|
||||
);
|
||||
return NewConnection::Filtered;
|
||||
}
|
||||
|
||||
let config = self.config.clone();
|
||||
let open_connections_for_ip = self.increment_connections_for_ip(&peer)?;
|
||||
*self.open_connections() += 1;
|
||||
|
||||
debug!(
|
||||
"[{}] Opening channel ({}/{} connections for IP, {} total).",
|
||||
peer,
|
||||
open_connections_for_ip,
|
||||
config.max_connections_per_ip,
|
||||
self.open_connections(),
|
||||
);
|
||||
|
||||
NewConnection::Accepted(Channel {
|
||||
client_addr: peer,
|
||||
closed_connections: self.closed_connections.clone(),
|
||||
transport: stream.fuse(),
|
||||
config,
|
||||
ghost: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_closed_connection(self: &mut Pin<&mut Self>, addr: &SocketAddr) {
|
||||
*self.open_connections() -= 1;
|
||||
debug!(
|
||||
"[{}] Closing channel. {} open connections remaining.",
|
||||
addr, self.open_connections
|
||||
);
|
||||
self.decrement_connections_for_ip(&addr);
|
||||
self.connections_per_ip().compact(0.1);
|
||||
}
|
||||
|
||||
fn increment_connections_for_ip(self: &mut Pin<&mut Self>, peer: &SocketAddr) -> Option<usize> {
|
||||
let max_connections_per_ip = self.config().max_connections_per_ip;
|
||||
let mut occupied;
|
||||
let mut connections_per_ip = self.connections_per_ip();
|
||||
let occupied = match connections_per_ip.entry(peer.ip()) {
|
||||
Entry::Vacant(vacant) => vacant.insert(0),
|
||||
Entry::Occupied(o) => {
|
||||
if *o.get() < max_connections_per_ip {
|
||||
// Store the reference outside the block to extend the lifetime.
|
||||
occupied = o;
|
||||
occupied.get_mut()
|
||||
} else {
|
||||
info!(
|
||||
"[{}] Opened max connections from IP ({}/{}).",
|
||||
peer,
|
||||
o.get(),
|
||||
max_connections_per_ip
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
*occupied += 1;
|
||||
Some(*occupied)
|
||||
}
|
||||
|
||||
fn decrement_connections_for_ip(self: &mut Pin<&mut Self>, addr: &SocketAddr) {
|
||||
let should_compact = match self.connections_per_ip().entry(addr.ip()) {
|
||||
Entry::Vacant(_) => {
|
||||
error!("[{}] Got vacant entry when closing connection.", addr);
|
||||
return;
|
||||
}
|
||||
Entry::Occupied(mut occupied) => {
|
||||
*occupied.get_mut() -= 1;
|
||||
if *occupied.get() == 0 {
|
||||
occupied.remove();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
if should_compact {
|
||||
self.connections_per_ip().compact(0.1);
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_listener<C>(
|
||||
self: &mut Pin<&mut Self>,
|
||||
cx: &LocalWaker,
|
||||
) -> Poll<Option<io::Result<NewConnection<Req, Resp, C>>>>
|
||||
where
|
||||
S: Stream<Item = Result<C, io::Error>>,
|
||||
C: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
||||
{
|
||||
match ready!(self.listener().poll_next_unpin(cx)?) {
|
||||
Some(codec) => Poll::Ready(Some(Ok(self.handle_new_connection(codec)))),
|
||||
None => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_closed_connections(self: &mut Pin<&mut Self>, cx: &LocalWaker) -> Poll<io::Result<()>> {
|
||||
match ready!(self.closed_connections_rx().poll_next_unpin(cx)) {
|
||||
Some(addr) => {
|
||||
self.handle_closed_connection(&addr);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
None => unreachable!("Holding a copy of closed_connections and didn't close it."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req, Resp, T> Stream for ConnectionFilter<S, Req, Resp>
|
||||
where
|
||||
S: Stream<Item = Result<T, io::Error>>,
|
||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
||||
{
|
||||
type Item = io::Result<Channel<Req, Resp, T>>;
|
||||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &LocalWaker,
|
||||
) -> Poll<Option<io::Result<Channel<Req, Resp, T>>>> {
|
||||
loop {
|
||||
match (self.poll_listener(cx)?, self.poll_closed_connections(cx)?) {
|
||||
(Poll::Ready(Some(NewConnection::Accepted(channel))), _) => {
|
||||
return Poll::Ready(Some(Ok(channel)))
|
||||
}
|
||||
(Poll::Ready(Some(NewConnection::Filtered)), _) | (_, Poll::Ready(())) => {
|
||||
trace!("Filtered a connection; {} open.", self.open_connections());
|
||||
continue;
|
||||
}
|
||||
(Poll::Pending, Poll::Pending) => return Poll::Pending,
|
||||
(Poll::Ready(None), Poll::Pending) => {
|
||||
if *self.open_connections() > 0 {
|
||||
trace!(
|
||||
"Listener closed; {} open connections.",
|
||||
self.open_connections()
|
||||
);
|
||||
return Poll::Pending;
|
||||
}
|
||||
trace!("Shutting down listener: all connections closed, and no more coming.");
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
609
rpc/src/server/mod.rs
Normal file
609
rpc/src/server/mod.rs
Normal file
@@ -0,0 +1,609 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! Provides a server that concurrently handles many connections sending multiplexed requests.
|
||||
|
||||
use crate::{
|
||||
context::Context, util::deadline_compat, util::AsDuration, util::Compact, ClientMessage,
|
||||
ClientMessageKind, Request, Response, ServerError, Transport,
|
||||
};
|
||||
use fnv::FnvHashMap;
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
future::{abortable, AbortHandle},
|
||||
prelude::*,
|
||||
ready,
|
||||
stream::Fuse,
|
||||
task::{LocalWaker, Poll},
|
||||
try_ready,
|
||||
};
|
||||
use humantime::format_rfc3339;
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use pin_utils::{unsafe_pinned, unsafe_unpinned};
|
||||
use std::{
|
||||
error::Error as StdError,
|
||||
io,
|
||||
marker::PhantomData,
|
||||
net::SocketAddr,
|
||||
pin::Pin,
|
||||
time::{Instant, SystemTime},
|
||||
};
|
||||
use tokio_timer::timeout;
|
||||
use trace::{self, TraceId};
|
||||
|
||||
mod filter;
|
||||
|
||||
/// Manages clients, serving multiplexed requests over each connection.
|
||||
#[derive(Debug)]
|
||||
pub struct Server<Req, Resp> {
|
||||
config: Config,
|
||||
ghost: PhantomData<(Req, Resp)>,
|
||||
}
|
||||
|
||||
impl<Req, Resp> Default for Server<Req, Resp> {
|
||||
fn default() -> Self {
|
||||
new(Config::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings that control the behavior of the server.
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Config {
|
||||
/// The maximum number of clients that can be connected to the server at once. When at the
|
||||
/// limit, existing connections are honored and new connections are rejected.
|
||||
pub max_connections: usize,
|
||||
/// The maximum number of clients per IP address that can be connected to the server at once.
|
||||
/// When an IP is at the limit, existing connections are honored and new connections on that IP
|
||||
/// address are rejected.
|
||||
pub max_connections_per_ip: usize,
|
||||
/// The maximum number of requests that can be in flight for each client. When a client is at
|
||||
/// the in-flight request limit, existing requests are fulfilled and new requests are rejected.
|
||||
/// Rejected requests are sent a response error.
|
||||
pub max_in_flight_requests_per_connection: usize,
|
||||
/// The number of responses per client that can be buffered server-side before being sent.
|
||||
/// `pending_response_buffer` controls the buffer size of the channel that a server's
|
||||
/// response tasks use to send responses to the client handler task.
|
||||
pub pending_response_buffer: usize,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
max_connections: 1_000_000,
|
||||
max_connections_per_ip: 1_000,
|
||||
max_in_flight_requests_per_connection: 1_000,
|
||||
pending_response_buffer: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new server with configuration specified `config`.
|
||||
pub fn new<Req, Resp>(config: Config) -> Server<Req, Resp> {
|
||||
Server {
|
||||
config,
|
||||
ghost: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp> Server<Req, Resp> {
|
||||
/// Returns the config for this server.
|
||||
pub fn config(&self) -> &Config {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Returns a stream of the incoming connections to the server.
|
||||
pub fn incoming<S, T>(
|
||||
self,
|
||||
listener: S,
|
||||
) -> impl Stream<Item = io::Result<Channel<Req, Resp, T>>>
|
||||
where
|
||||
Req: Send,
|
||||
Resp: Send,
|
||||
S: Stream<Item = io::Result<T>>,
|
||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
||||
{
|
||||
self::filter::ConnectionFilter::filter(listener, self.config.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// The future driving the server.
|
||||
#[derive(Debug)]
|
||||
pub struct Running<S, F> {
|
||||
incoming: S,
|
||||
request_handler: F,
|
||||
}
|
||||
|
||||
impl<S, F> Running<S, F> {
|
||||
unsafe_pinned!(incoming: S);
|
||||
unsafe_unpinned!(request_handler: F);
|
||||
}
|
||||
|
||||
impl<S, T, Req, Resp, F, Fut> Future for Running<S, F>
|
||||
where
|
||||
S: Sized + Stream<Item = io::Result<Channel<Req, Resp, T>>>,
|
||||
Req: Send + 'static,
|
||||
Resp: Send + 'static,
|
||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send + 'static,
|
||||
F: FnOnce(Context, Req) -> Fut + Send + 'static + Clone,
|
||||
Fut: Future<Output = io::Result<Resp>> + Send + 'static,
|
||||
{
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &LocalWaker) -> Poll<()> {
|
||||
while let Some(channel) = ready!(self.incoming().poll_next(cx)) {
|
||||
match channel {
|
||||
Ok(channel) => {
|
||||
let peer = channel.client_addr;
|
||||
if let Err(e) =
|
||||
crate::spawn(channel.respond_with(self.request_handler().clone()))
|
||||
{
|
||||
warn!("[{}] Failed to spawn connection handler: {:?}", peer, e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Incoming connection error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("Server shutting down.");
|
||||
return Poll::Ready(());
|
||||
}
|
||||
}
|
||||
|
||||
/// A utility trait enabling a stream to fluently chain a request handler.
|
||||
pub trait Handler<T, Req, Resp>
|
||||
where
|
||||
Self: Sized + Stream<Item = io::Result<Channel<Req, Resp, T>>>,
|
||||
Req: Send,
|
||||
Resp: Send,
|
||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
||||
{
|
||||
/// Responds to all requests with `request_handler`.
|
||||
fn respond_with<F, Fut>(self, request_handler: F) -> Running<Self, F>
|
||||
where
|
||||
F: FnOnce(Context, Req) -> Fut + Send + 'static + Clone,
|
||||
Fut: Future<Output = io::Result<Resp>> + Send + 'static,
|
||||
{
|
||||
Running {
|
||||
incoming: self,
|
||||
request_handler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Req, Resp, S> Handler<T, Req, Resp> for S
|
||||
where
|
||||
S: Sized + Stream<Item = io::Result<Channel<Req, Resp, T>>>,
|
||||
Req: Send,
|
||||
Resp: Send,
|
||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
||||
{}
|
||||
|
||||
/// Responds to all requests with `request_handler`.
|
||||
/// The server end of an open connection with a client.
|
||||
#[derive(Debug)]
|
||||
pub struct Channel<Req, Resp, T> {
|
||||
/// Writes responses to the wire and reads requests off the wire.
|
||||
transport: Fuse<T>,
|
||||
/// Signals the connection is closed when `Channel` is dropped.
|
||||
closed_connections: mpsc::UnboundedSender<SocketAddr>,
|
||||
/// Channel limits to prevent unlimited resource usage.
|
||||
config: Config,
|
||||
/// The address of the server connected to.
|
||||
client_addr: SocketAddr,
|
||||
/// Types the request and response.
|
||||
ghost: PhantomData<(Req, Resp)>,
|
||||
}
|
||||
|
||||
impl<Req, Resp, T> Drop for Channel<Req, Resp, T> {
|
||||
fn drop(&mut self) {
|
||||
trace!("[{}] Closing channel.", self.client_addr);
|
||||
|
||||
// Even in a bounded channel, each connection would have a guaranteed slot, so using
|
||||
// an unbounded sender is actually no different. And, the bound is on the maximum number
|
||||
// of open connections.
|
||||
if self
|
||||
.closed_connections
|
||||
.unbounded_send(self.client_addr)
|
||||
.is_err()
|
||||
{
|
||||
warn!(
|
||||
"[{}] Failed to send closed connection message.",
|
||||
self.client_addr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, T> Channel<Req, Resp, T> {
|
||||
unsafe_pinned!(transport: Fuse<T>);
|
||||
}
|
||||
|
||||
impl<Req, Resp, T> Channel<Req, Resp, T>
|
||||
where
|
||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
||||
Req: Send,
|
||||
Resp: Send,
|
||||
{
|
||||
pub(crate) fn start_send(
|
||||
self: &mut Pin<&mut Self>,
|
||||
response: Response<Resp>,
|
||||
) -> io::Result<()> {
|
||||
self.transport().start_send(response)
|
||||
}
|
||||
|
||||
pub(crate) fn poll_ready(self: &mut Pin<&mut Self>, cx: &LocalWaker) -> Poll<io::Result<()>> {
|
||||
self.transport().poll_ready(cx)
|
||||
}
|
||||
|
||||
pub(crate) fn poll_flush(self: &mut Pin<&mut Self>, cx: &LocalWaker) -> Poll<io::Result<()>> {
|
||||
self.transport().poll_flush(cx)
|
||||
}
|
||||
|
||||
pub(crate) fn poll_next(
|
||||
self: &mut Pin<&mut Self>,
|
||||
cx: &LocalWaker,
|
||||
) -> Poll<Option<io::Result<ClientMessage<Req>>>> {
|
||||
self.transport().poll_next(cx)
|
||||
}
|
||||
|
||||
/// Returns the address of the client connected to the channel.
|
||||
pub fn client_addr(&self) -> &SocketAddr {
|
||||
&self.client_addr
|
||||
}
|
||||
|
||||
/// Respond to requests coming over the channel with `f`. Returns a future that drives the
|
||||
/// responses and resolves when the connection is closed.
|
||||
pub fn respond_with<F, Fut>(self, f: F) -> impl Future<Output = ()>
|
||||
where
|
||||
F: FnOnce(Context, Req) -> Fut + Send + 'static + Clone,
|
||||
Fut: Future<Output = io::Result<Resp>> + Send + 'static,
|
||||
Req: 'static,
|
||||
Resp: 'static,
|
||||
{
|
||||
let (responses_tx, responses) = mpsc::channel(self.config.pending_response_buffer);
|
||||
let responses = responses.fuse();
|
||||
let peer = self.client_addr;
|
||||
|
||||
ClientHandler {
|
||||
channel: self,
|
||||
f,
|
||||
pending_responses: responses,
|
||||
responses_tx,
|
||||
in_flight_requests: FnvHashMap::default(),
|
||||
}
|
||||
.unwrap_or_else(move |e| {
|
||||
info!("[{}] ClientHandler errored out: {}", peer, e);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ClientHandler<Req, Resp, T, F> {
|
||||
channel: Channel<Req, Resp, T>,
|
||||
/// Responses waiting to be written to the wire.
|
||||
pending_responses: Fuse<mpsc::Receiver<(Context, Response<Resp>)>>,
|
||||
/// Handed out to request handlers to fan in responses.
|
||||
responses_tx: mpsc::Sender<(Context, Response<Resp>)>,
|
||||
/// Number of requests currently being responded to.
|
||||
in_flight_requests: FnvHashMap<u64, AbortHandle>,
|
||||
/// Request handler.
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl<Req, Resp, T, F> ClientHandler<Req, Resp, T, F> {
|
||||
unsafe_pinned!(channel: Channel<Req, Resp, T>);
|
||||
unsafe_pinned!(in_flight_requests: FnvHashMap<u64, AbortHandle>);
|
||||
unsafe_pinned!(pending_responses: Fuse<mpsc::Receiver<(Context, Response<Resp>)>>);
|
||||
unsafe_pinned!(responses_tx: mpsc::Sender<(Context, Response<Resp>)>);
|
||||
// For this to be safe, field f must be private, and code in this module must never
|
||||
// construct PinMut<F>.
|
||||
unsafe_unpinned!(f: F);
|
||||
}
|
||||
|
||||
impl<Req, Resp, T, F, Fut> ClientHandler<Req, Resp, T, F>
|
||||
where
|
||||
Req: Send + 'static,
|
||||
Resp: Send + 'static,
|
||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
||||
F: FnOnce(Context, Req) -> Fut + Send + 'static + Clone,
|
||||
Fut: Future<Output = io::Result<Resp>> + Send + 'static,
|
||||
{
|
||||
/// If at max in-flight requests, check that there's room to immediately write a throttled
|
||||
/// response.
|
||||
fn poll_ready_if_throttling(
|
||||
self: &mut Pin<&mut Self>,
|
||||
cx: &LocalWaker,
|
||||
) -> Poll<io::Result<()>> {
|
||||
if self.in_flight_requests.len()
|
||||
>= self.channel.config.max_in_flight_requests_per_connection
|
||||
{
|
||||
let peer = self.channel().client_addr;
|
||||
|
||||
while let Poll::Pending = self.channel().poll_ready(cx)? {
|
||||
info!(
|
||||
"[{}] In-flight requests at max ({}), and transport is not ready.",
|
||||
peer,
|
||||
self.in_flight_requests().len(),
|
||||
);
|
||||
try_ready!(self.channel().poll_flush(cx));
|
||||
}
|
||||
}
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn pump_read(self: &mut Pin<&mut Self>, cx: &LocalWaker) -> Poll<Option<io::Result<()>>> {
|
||||
ready!(self.poll_ready_if_throttling(cx)?);
|
||||
|
||||
Poll::Ready(match ready!(self.channel().poll_next(cx)?) {
|
||||
Some(message) => {
|
||||
match message.message {
|
||||
ClientMessageKind::Request(request) => {
|
||||
self.handle_request(message.trace_context, request)?;
|
||||
}
|
||||
ClientMessageKind::Cancel { request_id } => {
|
||||
self.cancel_request(&message.trace_context, request_id);
|
||||
}
|
||||
}
|
||||
Some(Ok(()))
|
||||
}
|
||||
None => {
|
||||
trace!("[{}] Read half closed", self.channel.client_addr);
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn pump_write(
|
||||
self: &mut Pin<&mut Self>,
|
||||
cx: &LocalWaker,
|
||||
read_half_closed: bool,
|
||||
) -> Poll<Option<io::Result<()>>> {
|
||||
match self.poll_next_response(cx)? {
|
||||
Poll::Ready(Some((_, response))) => {
|
||||
self.channel().start_send(response)?;
|
||||
Poll::Ready(Some(Ok(())))
|
||||
}
|
||||
Poll::Ready(None) => {
|
||||
// Shutdown can't be done before we finish pumping out remaining responses.
|
||||
ready!(self.channel().poll_flush(cx)?);
|
||||
Poll::Ready(None)
|
||||
}
|
||||
Poll::Pending => {
|
||||
// No more requests to process, so flush any requests buffered in the transport.
|
||||
ready!(self.channel().poll_flush(cx)?);
|
||||
|
||||
// Being here means there are no staged requests and all written responses are
|
||||
// fully flushed. So, if the read half is closed and there are no in-flight
|
||||
// requests, then we can close the write half.
|
||||
if read_half_closed && self.in_flight_requests().is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_next_response(
|
||||
self: &mut Pin<&mut Self>,
|
||||
cx: &LocalWaker,
|
||||
) -> Poll<Option<io::Result<(Context, Response<Resp>)>>> {
|
||||
// Ensure there's room to write a response.
|
||||
while let Poll::Pending = self.channel().poll_ready(cx)? {
|
||||
ready!(self.channel().poll_flush(cx)?);
|
||||
}
|
||||
|
||||
let peer = self.channel().client_addr;
|
||||
|
||||
match ready!(self.pending_responses().poll_next(cx)) {
|
||||
Some((ctx, response)) => {
|
||||
if let Some(_) = self.in_flight_requests().remove(&response.request_id) {
|
||||
self.in_flight_requests().compact(0.1);
|
||||
}
|
||||
trace!(
|
||||
"[{}/{}] Staging response. In-flight requests = {}.",
|
||||
ctx.trace_id(),
|
||||
peer,
|
||||
self.in_flight_requests().len(),
|
||||
);
|
||||
return Poll::Ready(Some(Ok((ctx, response))));
|
||||
}
|
||||
None => {
|
||||
// This branch likely won't happen, since the ClientHandler is holding a Sender.
|
||||
trace!("[{}] No new responses.", peer);
|
||||
Poll::Ready(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_request(
|
||||
self: &mut Pin<&mut Self>,
|
||||
trace_context: trace::Context,
|
||||
request: Request<Req>,
|
||||
) -> io::Result<()> {
|
||||
let request_id = request.id;
|
||||
let peer = self.channel().client_addr;
|
||||
let ctx = Context {
|
||||
deadline: request.deadline,
|
||||
trace_context,
|
||||
};
|
||||
let request = request.message;
|
||||
|
||||
if self.in_flight_requests().len()
|
||||
>= self.channel().config.max_in_flight_requests_per_connection
|
||||
{
|
||||
debug!(
|
||||
"[{}/{}] Client has reached in-flight request limit ({}/{}).",
|
||||
ctx.trace_id(),
|
||||
peer,
|
||||
self.in_flight_requests().len(),
|
||||
self.channel().config.max_in_flight_requests_per_connection
|
||||
);
|
||||
|
||||
self.channel().start_send(Response {
|
||||
request_id,
|
||||
message: Err(ServerError {
|
||||
kind: io::ErrorKind::WouldBlock,
|
||||
detail: Some("Server throttled the request.".into()),
|
||||
}),
|
||||
})?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let deadline = ctx.deadline;
|
||||
let timeout = deadline.as_duration();
|
||||
trace!(
|
||||
"[{}/{}] Received request with deadline {} (timeout {:?}).",
|
||||
ctx.trace_id(),
|
||||
peer,
|
||||
format_rfc3339(deadline),
|
||||
timeout,
|
||||
);
|
||||
let mut response_tx = self.responses_tx().clone();
|
||||
|
||||
let trace_id = *ctx.trace_id();
|
||||
let response = self.f().clone()(ctx.clone(), request);
|
||||
let response = deadline_compat::Deadline::new(response, Instant::now() + timeout).then(
|
||||
async move |result| {
|
||||
let response = Response {
|
||||
request_id,
|
||||
message: match result {
|
||||
Ok(message) => Ok(message),
|
||||
Err(e) => Err(make_server_error(e, trace_id, peer, deadline)),
|
||||
},
|
||||
};
|
||||
trace!("[{}/{}] Sending response.", trace_id, peer);
|
||||
await!(response_tx.send((ctx, response)).unwrap_or_else(|_| ()));
|
||||
},
|
||||
);
|
||||
let (abortable_response, abort_handle) = abortable(response);
|
||||
crate::spawn(abortable_response.map(|_| ())).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"Could not spawn response task. Is shutdown: {}",
|
||||
e.is_shutdown()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
self.in_flight_requests().insert(request_id, abort_handle);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cancel_request(self: &mut Pin<&mut Self>, trace_context: &trace::Context, request_id: u64) {
|
||||
// It's possible the request was already completed, so it's fine
|
||||
// if this is None.
|
||||
if let Some(cancel_handle) = self.in_flight_requests().remove(&request_id) {
|
||||
self.in_flight_requests().compact(0.1);
|
||||
|
||||
cancel_handle.abort();
|
||||
let remaining = self.in_flight_requests().len();
|
||||
trace!(
|
||||
"[{}/{}] Request canceled. In-flight requests = {}",
|
||||
trace_context.trace_id,
|
||||
self.channel.client_addr,
|
||||
remaining,
|
||||
);
|
||||
} else {
|
||||
trace!(
|
||||
"[{}/{}] Received cancellation, but response handler \
|
||||
is already complete.",
|
||||
trace_context.trace_id,
|
||||
self.channel.client_addr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, T, F, Fut> Future for ClientHandler<Req, Resp, T, F>
|
||||
where
|
||||
Req: Send + 'static,
|
||||
Resp: Send + 'static,
|
||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
||||
F: FnOnce(Context, Req) -> Fut + Send + 'static + Clone,
|
||||
Fut: Future<Output = io::Result<Resp>> + Send + 'static,
|
||||
{
|
||||
type Output = io::Result<()>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &LocalWaker) -> Poll<io::Result<()>> {
|
||||
trace!("[{}] ClientHandler::poll", self.channel.client_addr);
|
||||
loop {
|
||||
let read = self.pump_read(cx)?;
|
||||
match (read, self.pump_write(cx, read == Poll::Ready(None))?) {
|
||||
(Poll::Ready(None), Poll::Ready(None)) => {
|
||||
info!("[{}] Client disconnected.", self.channel.client_addr);
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
(read @ Poll::Ready(Some(())), write) | (read, write @ Poll::Ready(Some(()))) => {
|
||||
trace!(
|
||||
"[{}] read: {:?}, write: {:?}.",
|
||||
self.channel.client_addr,
|
||||
read,
|
||||
write
|
||||
)
|
||||
}
|
||||
(read, write) => {
|
||||
trace!(
|
||||
"[{}] read: {:?}, write: {:?} (not ready).",
|
||||
self.channel.client_addr,
|
||||
read,
|
||||
write,
|
||||
);
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_server_error(
|
||||
e: timeout::Error<io::Error>,
|
||||
trace_id: TraceId,
|
||||
peer: SocketAddr,
|
||||
deadline: SystemTime,
|
||||
) -> ServerError {
|
||||
if e.is_elapsed() {
|
||||
debug!(
|
||||
"[{}/{}] Response did not complete before deadline of {}s.",
|
||||
trace_id,
|
||||
peer,
|
||||
format_rfc3339(deadline)
|
||||
);
|
||||
// No point in responding, since the client will have dropped the request.
|
||||
ServerError {
|
||||
kind: io::ErrorKind::TimedOut,
|
||||
detail: Some(format!(
|
||||
"Response did not complete before deadline of {}s.",
|
||||
format_rfc3339(deadline)
|
||||
)),
|
||||
}
|
||||
} else if e.is_timer() {
|
||||
error!(
|
||||
"[{}/{}] Response failed because of an issue with a timer: {}",
|
||||
trace_id, peer, e
|
||||
);
|
||||
|
||||
ServerError {
|
||||
kind: io::ErrorKind::Other,
|
||||
detail: Some(format!("{}", e)),
|
||||
}
|
||||
} else if e.is_inner() {
|
||||
let e = e.into_inner().unwrap();
|
||||
ServerError {
|
||||
kind: e.kind(),
|
||||
detail: Some(e.description().into()),
|
||||
}
|
||||
} else {
|
||||
error!("[{}/{}] Unexpected response failure: {}", trace_id, peer, e);
|
||||
|
||||
ServerError {
|
||||
kind: io::ErrorKind::Other,
|
||||
detail: Some(format!("Server unexpectedly failed to respond: {}", e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
158
rpc/src/transport/channel.rs
Normal file
158
rpc/src/transport/channel.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! Transports backed by in-memory channels.
|
||||
|
||||
use crate::Transport;
|
||||
use futures::{channel::mpsc, task::LocalWaker, Poll, Sink, Stream};
|
||||
use pin_utils::unsafe_pinned;
|
||||
use std::pin::Pin;
|
||||
use std::{
|
||||
io,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
};
|
||||
|
||||
/// Returns two unbounded channel peers. Each [`Stream`] yields items sent through the other's
|
||||
/// [`Sink`].
|
||||
pub fn unbounded<SinkItem, Item>() -> (
|
||||
UnboundedChannel<SinkItem, Item>,
|
||||
UnboundedChannel<Item, SinkItem>,
|
||||
) {
|
||||
let (tx1, rx2) = mpsc::unbounded();
|
||||
let (tx2, rx1) = mpsc::unbounded();
|
||||
(
|
||||
UnboundedChannel { tx: tx1, rx: rx1 },
|
||||
UnboundedChannel { tx: tx2, rx: rx2 },
|
||||
)
|
||||
}
|
||||
|
||||
/// A bi-directional channel backed by an [`UnboundedSender`](mpsc::UnboundedSender)
|
||||
/// and [`UnboundedReceiver`](mpsc::UnboundedReceiver).
|
||||
#[derive(Debug)]
|
||||
pub struct UnboundedChannel<Item, SinkItem> {
|
||||
rx: mpsc::UnboundedReceiver<Item>,
|
||||
tx: mpsc::UnboundedSender<SinkItem>,
|
||||
}
|
||||
|
||||
impl<Item, SinkItem> UnboundedChannel<Item, SinkItem> {
|
||||
unsafe_pinned!(rx: mpsc::UnboundedReceiver<Item>);
|
||||
unsafe_pinned!(tx: mpsc::UnboundedSender<SinkItem>);
|
||||
}
|
||||
|
||||
impl<Item, SinkItem> Stream for UnboundedChannel<Item, SinkItem> {
|
||||
type Item = Result<Item, io::Error>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &LocalWaker) -> Poll<Option<io::Result<Item>>> {
|
||||
self.rx().poll_next(cx).map(|option| option.map(Ok))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Item, SinkItem> Sink for UnboundedChannel<Item, SinkItem> {
|
||||
type SinkItem = SinkItem;
|
||||
type SinkError = io::Error;
|
||||
|
||||
fn poll_ready(mut self: Pin<&mut Self>, cx: &LocalWaker) -> Poll<io::Result<()>> {
|
||||
self.tx()
|
||||
.poll_ready(cx)
|
||||
.map_err(|_| io::Error::from(io::ErrorKind::NotConnected))
|
||||
}
|
||||
|
||||
fn start_send(mut self: Pin<&mut Self>, item: SinkItem) -> io::Result<()> {
|
||||
self.tx()
|
||||
.start_send(item)
|
||||
.map_err(|_| io::Error::from(io::ErrorKind::NotConnected))
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &LocalWaker) -> Poll<Result<(), Self::SinkError>> {
|
||||
self.tx()
|
||||
.poll_flush(cx)
|
||||
.map_err(|_| io::Error::from(io::ErrorKind::NotConnected))
|
||||
}
|
||||
|
||||
fn poll_close(mut self: Pin<&mut Self>, cx: &LocalWaker) -> Poll<io::Result<()>> {
|
||||
self.tx()
|
||||
.poll_close(cx)
|
||||
.map_err(|_| io::Error::from(io::ErrorKind::NotConnected))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Item, SinkItem> Transport for UnboundedChannel<Item, SinkItem> {
|
||||
type Item = Item;
|
||||
type SinkItem = SinkItem;
|
||||
|
||||
fn peer_addr(&self) -> io::Result<SocketAddr> {
|
||||
Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
|
||||
}
|
||||
|
||||
fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||
Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
client, context,
|
||||
server::{Handler, Server},
|
||||
transport,
|
||||
};
|
||||
use futures::{compat::TokioDefaultSpawner, prelude::*, stream};
|
||||
use log::trace;
|
||||
use std::io;
|
||||
|
||||
#[test]
|
||||
fn integration() {
|
||||
let _ = env_logger::try_init();
|
||||
crate::init(TokioDefaultSpawner);
|
||||
|
||||
let (client_channel, server_channel) = transport::channel::unbounded();
|
||||
let server = Server::<String, u64>::default()
|
||||
.incoming(stream::once(future::ready(Ok(server_channel))))
|
||||
.respond_with(|_ctx, request| {
|
||||
future::ready(request.parse::<u64>().map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("{:?} is not an int", request),
|
||||
)
|
||||
}))
|
||||
});
|
||||
|
||||
let responses = async {
|
||||
let mut client = await!(client::new(client::Config::default(), client_channel))?;
|
||||
|
||||
let response1 = await!(client.call(context::current(), "123".into()));
|
||||
let response2 = await!(client.call(context::current(), "abc".into()));
|
||||
|
||||
Ok::<_, io::Error>((response1, response2))
|
||||
};
|
||||
|
||||
let (response1, response2) =
|
||||
run_future(server.join(responses.unwrap_or_else(|e| panic!(e)))).1;
|
||||
|
||||
trace!("response1: {:?}, response2: {:?}", response1, response2);
|
||||
|
||||
assert!(response1.is_ok());
|
||||
assert_eq!(response1.ok().unwrap(), 123);
|
||||
|
||||
assert!(response2.is_err());
|
||||
assert_eq!(response2.err().unwrap().kind(), io::ErrorKind::InvalidInput);
|
||||
}
|
||||
|
||||
fn run_future<F>(f: F) -> F::Output
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
tokio::run(
|
||||
f.map(|result| tx.send(result).unwrap_or_else(|_| unreachable!()))
|
||||
.boxed()
|
||||
.unit_error()
|
||||
.compat(),
|
||||
);
|
||||
futures::executor::block_on(rx).unwrap()
|
||||
}
|
||||
}
|
||||
121
rpc/src/transport/mod.rs
Normal file
121
rpc/src/transport/mod.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! Provides a [`Transport`] trait as well as implementations.
|
||||
//!
|
||||
//! The rpc crate is transport- and protocol-agnostic. Any transport that impls [`Transport`]
|
||||
//! can be plugged in, using whatever protocol it wants.
|
||||
|
||||
use futures::prelude::*;
|
||||
use std::{
|
||||
io,
|
||||
net::SocketAddr,
|
||||
pin::Pin,
|
||||
task::{LocalWaker, Poll},
|
||||
};
|
||||
|
||||
pub mod channel;
|
||||
|
||||
/// A bidirectional stream ([`Sink`] + [`Stream`]) of messages.
|
||||
pub trait Transport
|
||||
where
|
||||
Self: Stream<Item = io::Result<<Self as Transport>::Item>>,
|
||||
Self: Sink<SinkItem = <Self as Transport>::SinkItem, SinkError = io::Error>,
|
||||
{
|
||||
/// The type read off the transport.
|
||||
type Item;
|
||||
/// The type written to the transport.
|
||||
type SinkItem;
|
||||
|
||||
/// The address of the remote peer this transport is in communication with.
|
||||
fn peer_addr(&self) -> io::Result<SocketAddr>;
|
||||
/// The address of the local half of this transport.
|
||||
fn local_addr(&self) -> io::Result<SocketAddr>;
|
||||
}
|
||||
|
||||
/// Returns a new Transport backed by the given Stream + Sink and connecting addresses.
|
||||
pub fn new<S, Item>(
|
||||
inner: S,
|
||||
peer_addr: SocketAddr,
|
||||
local_addr: SocketAddr,
|
||||
) -> impl Transport<Item = Item, SinkItem = S::SinkItem>
|
||||
where
|
||||
S: Stream<Item = io::Result<Item>>,
|
||||
S: Sink<SinkError = io::Error>,
|
||||
{
|
||||
TransportShim {
|
||||
inner,
|
||||
peer_addr,
|
||||
local_addr,
|
||||
}
|
||||
}
|
||||
|
||||
/// A transport created by adding peers to a Stream + Sink.
|
||||
#[derive(Debug)]
|
||||
struct TransportShim<S> {
|
||||
peer_addr: SocketAddr,
|
||||
local_addr: SocketAddr,
|
||||
inner: S,
|
||||
}
|
||||
|
||||
impl<S> TransportShim<S> {
|
||||
pin_utils::unsafe_pinned!(inner: S);
|
||||
}
|
||||
|
||||
impl<S> Stream for TransportShim<S>
|
||||
where
|
||||
S: Stream,
|
||||
{
|
||||
type Item = S::Item;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Option<S::Item>> {
|
||||
self.inner().poll_next(waker)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Sink for TransportShim<S>
|
||||
where
|
||||
S: Sink,
|
||||
{
|
||||
type SinkItem = S::SinkItem;
|
||||
type SinkError = S::SinkError;
|
||||
|
||||
fn start_send(mut self: Pin<&mut Self>, item: S::SinkItem) -> Result<(), S::SinkError> {
|
||||
self.inner().start_send(item)
|
||||
}
|
||||
|
||||
fn poll_ready(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Result<(), S::SinkError>> {
|
||||
self.inner().poll_ready(waker)
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Result<(), S::SinkError>> {
|
||||
self.inner().poll_flush(waker)
|
||||
}
|
||||
|
||||
fn poll_close(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Result<(), S::SinkError>> {
|
||||
self.inner().poll_close(waker)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Item> Transport for TransportShim<S>
|
||||
where
|
||||
S: Stream + Sink,
|
||||
Self: Stream<Item = io::Result<Item>>,
|
||||
Self: Sink<SinkItem = S::SinkItem, SinkError = io::Error>,
|
||||
{
|
||||
type Item = Item;
|
||||
type SinkItem = S::SinkItem;
|
||||
|
||||
/// The address of the remote peer this transport is in communication with.
|
||||
fn peer_addr(&self) -> io::Result<SocketAddr> {
|
||||
Ok(self.peer_addr)
|
||||
}
|
||||
|
||||
/// The address of the local half of this transport.
|
||||
fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||
Ok(self.local_addr)
|
||||
}
|
||||
}
|
||||
69
rpc/src/util/deadline_compat.rs
Normal file
69
rpc/src/util/deadline_compat.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use futures::{
|
||||
compat::{Compat01As03, Future01CompatExt},
|
||||
prelude::*,
|
||||
ready,
|
||||
task::{LocalWaker, Poll},
|
||||
};
|
||||
use pin_utils::unsafe_pinned;
|
||||
use std::pin::Pin;
|
||||
use std::time::Instant;
|
||||
use tokio_timer::{timeout, Delay};
|
||||
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
#[derive(Debug)]
|
||||
pub struct Deadline<T> {
|
||||
future: T,
|
||||
delay: Compat01As03<Delay>,
|
||||
}
|
||||
|
||||
impl<T> Deadline<T> {
|
||||
unsafe_pinned!(future: T);
|
||||
unsafe_pinned!(delay: Compat01As03<Delay>);
|
||||
|
||||
/// Create a new `Deadline` that completes when `future` completes or when
|
||||
/// `deadline` is reached.
|
||||
pub fn new(future: T, deadline: Instant) -> Deadline<T> {
|
||||
Deadline::new_with_delay(future, Delay::new(deadline))
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_delay(future: T, delay: Delay) -> Deadline<T> {
|
||||
Deadline {
|
||||
future,
|
||||
delay: delay.compat(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the underlying future in this deadline.
|
||||
pub fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.future
|
||||
}
|
||||
}
|
||||
impl<T> Future for Deadline<T>
|
||||
where
|
||||
T: TryFuture,
|
||||
{
|
||||
type Output = Result<T::Ok, timeout::Error<T::Error>>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Self::Output> {
|
||||
// First, try polling the future
|
||||
match self.future().try_poll(waker) {
|
||||
Poll::Ready(Ok(v)) => return Poll::Ready(Ok(v)),
|
||||
Poll::Pending => {}
|
||||
Poll::Ready(Err(e)) => return Poll::Ready(Err(timeout::Error::inner(e))),
|
||||
}
|
||||
|
||||
let delay = self.delay().poll_unpin(waker);
|
||||
|
||||
// Now check the timer
|
||||
match ready!(delay) {
|
||||
Ok(_) => Poll::Ready(Err(timeout::Error::elapsed())),
|
||||
Err(e) => Poll::Ready(Err(timeout::Error::timer(e))),
|
||||
}
|
||||
}
|
||||
}
|
||||
46
rpc/src/util/mod.rs
Normal file
46
rpc/src/util/mod.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
hash::{BuildHasher, Hash},
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
pub mod deadline_compat;
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod serde;
|
||||
|
||||
/// Types that can be represented by a [`Duration`].
|
||||
pub trait AsDuration {
|
||||
fn as_duration(&self) -> Duration;
|
||||
}
|
||||
|
||||
impl AsDuration for SystemTime {
|
||||
/// Duration of 0 if self is earlier than [`SystemTime::now`].
|
||||
fn as_duration(&self) -> Duration {
|
||||
self.duration_since(SystemTime::now()).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Collection compaction; configurable `shrink_to_fit`.
|
||||
pub trait Compact {
|
||||
/// Compacts space if the ratio of length : capacity is less than `usage_ratio_threshold`.
|
||||
fn compact(&mut self, usage_ratio_threshold: f64);
|
||||
}
|
||||
|
||||
impl<K, V, H> Compact for HashMap<K, V, H>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
H: BuildHasher,
|
||||
{
|
||||
fn compact(&mut self, usage_ratio_threshold: f64) {
|
||||
let usage_ratio = self.len() as f64 / self.capacity() as f64;
|
||||
if usage_ratio < usage_ratio_threshold {
|
||||
self.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
}
|
||||
95
rpc/src/util/serde.rs
Normal file
95
rpc/src/util/serde.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::{
|
||||
io,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
/// Serializes `system_time` as a `u64` equal to the number of seconds since the epoch.
|
||||
pub fn serialize_epoch_secs<S>(system_time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
system_time
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap_or(Duration::from_secs(0))
|
||||
.as_secs() // Only care about second precision
|
||||
.serialize(serializer)
|
||||
}
|
||||
|
||||
/// Deserializes [`SystemTime`] from a `u64` equal to the number of seconds since the epoch.
|
||||
pub fn deserialize_epoch_secs<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(SystemTime::UNIX_EPOCH + Duration::from_secs(u64::deserialize(deserializer)?))
|
||||
}
|
||||
|
||||
/// Serializes [`io::ErrorKind`] as a `u32`.
|
||||
pub fn serialize_io_error_kind_as_u32<S>(
|
||||
kind: &io::ErrorKind,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
use std::io::ErrorKind::*;
|
||||
match *kind {
|
||||
NotFound => 0,
|
||||
PermissionDenied => 1,
|
||||
ConnectionRefused => 2,
|
||||
ConnectionReset => 3,
|
||||
ConnectionAborted => 4,
|
||||
NotConnected => 5,
|
||||
AddrInUse => 6,
|
||||
AddrNotAvailable => 7,
|
||||
BrokenPipe => 8,
|
||||
AlreadyExists => 9,
|
||||
WouldBlock => 10,
|
||||
InvalidInput => 11,
|
||||
InvalidData => 12,
|
||||
TimedOut => 13,
|
||||
WriteZero => 14,
|
||||
Interrupted => 15,
|
||||
Other => 16,
|
||||
UnexpectedEof => 17,
|
||||
_ => 16,
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
|
||||
/// Deserializes [`io::ErrorKind`] from a `u32`.
|
||||
pub fn deserialize_io_error_kind_from_u32<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<io::ErrorKind, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
use std::io::ErrorKind::*;
|
||||
Ok(match u32::deserialize(deserializer)? {
|
||||
0 => NotFound,
|
||||
1 => PermissionDenied,
|
||||
2 => ConnectionRefused,
|
||||
3 => ConnectionReset,
|
||||
4 => ConnectionAborted,
|
||||
5 => NotConnected,
|
||||
6 => AddrInUse,
|
||||
7 => AddrNotAvailable,
|
||||
8 => BrokenPipe,
|
||||
9 => AlreadyExists,
|
||||
10 => WouldBlock,
|
||||
11 => InvalidInput,
|
||||
12 => InvalidData,
|
||||
13 => TimedOut,
|
||||
14 => WriteZero,
|
||||
15 => Interrupted,
|
||||
16 => Other,
|
||||
17 => UnexpectedEof,
|
||||
_ => Other,
|
||||
})
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
reorder_imports = true
|
||||
@@ -1,91 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt, io};
|
||||
use std::error::Error as StdError;
|
||||
|
||||
/// All errors that can occur during the use of tarpc.
|
||||
#[derive(Debug)]
|
||||
pub enum Error<E> {
|
||||
/// Any IO error.
|
||||
Io(io::Error),
|
||||
/// Error deserializing the server response.
|
||||
///
|
||||
/// Typically this indicates a faulty implementation of `serde::Serialize` or
|
||||
/// `serde::Deserialize`.
|
||||
ResponseDeserialize(::bincode::Error),
|
||||
/// Error deserializing the client request.
|
||||
///
|
||||
/// Typically this indicates a faulty implementation of `serde::Serialize` or
|
||||
/// `serde::Deserialize`.
|
||||
RequestDeserialize(String),
|
||||
/// The server was unable to reply to the rpc for some reason.
|
||||
///
|
||||
/// This is a service-specific error. Its type is individually specified in the
|
||||
/// `service!` macro for each rpc.
|
||||
App(E),
|
||||
}
|
||||
|
||||
impl<'a, E: StdError + Deserialize<'a> + Serialize + Send + 'static> fmt::Display for Error<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::ResponseDeserialize(ref e) => write!(f, r#"{}: "{}""#, self.description(), e),
|
||||
Error::RequestDeserialize(ref e) => write!(f, r#"{}: "{}""#, self.description(), e),
|
||||
Error::App(ref e) => fmt::Display::fmt(e, f),
|
||||
Error::Io(ref e) => fmt::Display::fmt(e, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E: StdError + Deserialize<'a> + Serialize + Send + 'static> StdError for Error<E> {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::ResponseDeserialize(_) => "The client failed to deserialize the response.",
|
||||
Error::RequestDeserialize(_) => "The server failed to deserialize the request.",
|
||||
Error::App(ref e) => e.description(),
|
||||
Error::Io(ref e) => e.description(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&StdError> {
|
||||
match *self {
|
||||
Error::ResponseDeserialize(ref e) => e.cause(),
|
||||
Error::RequestDeserialize(_) | Error::App(_) => None,
|
||||
Error::Io(ref e) => e.cause(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<io::Error> for Error<E> {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<WireError<E>> for Error<E> {
|
||||
fn from(err: WireError<E>) -> Self {
|
||||
match err {
|
||||
WireError::RequestDeserialize(s) => Error::RequestDeserialize(s),
|
||||
WireError::App(e) => Error::App(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A serializable, server-supplied error.
|
||||
#[doc(hidden)]
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub enum WireError<E> {
|
||||
/// Server-side error in deserializing the client request.
|
||||
RequestDeserialize(String),
|
||||
/// The server was unable to reply to the rpc for some reason.
|
||||
App(E),
|
||||
}
|
||||
|
||||
/// Convert `native_tls::Error` to `std::io::Error`
|
||||
#[cfg(feature = "tls")]
|
||||
pub fn native_to_io(e: ::native_tls::Error) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
use {REMOTE, bincode};
|
||||
use future::server::Response;
|
||||
use futures::{self, Future, future};
|
||||
use protocol::Proto;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use stream_type::StreamType;
|
||||
use tokio_core::net::TcpStream;
|
||||
use tokio_core::reactor;
|
||||
use tokio_proto::BindClient as ProtoBindClient;
|
||||
use tokio_proto::multiplex::ClientService;
|
||||
use tokio_service::Service;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "tls")] {
|
||||
use errors::native_to_io;
|
||||
use tls::client::Context;
|
||||
use tokio_tls::TlsConnectorExt;
|
||||
} else {}
|
||||
}
|
||||
|
||||
/// Additional options to configure how the client connects and operates.
|
||||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
/// Max packet size in bytes.
|
||||
max_payload_size: u64,
|
||||
reactor: Option<Reactor>,
|
||||
#[cfg(feature = "tls")]
|
||||
tls_ctx: Option<Context>,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
#[cfg(feature = "tls")]
|
||||
fn default() -> Self {
|
||||
Options {
|
||||
max_payload_size: 2 << 20,
|
||||
reactor: None,
|
||||
tls_ctx: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tls"))]
|
||||
fn default() -> Self {
|
||||
Options {
|
||||
max_payload_size: 2 << 20,
|
||||
reactor: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
/// Set the max payload size in bytes. The default is 2 << 20 (2 MiB).
|
||||
pub fn max_payload_size(mut self, bytes: u64) -> Self {
|
||||
self.max_payload_size = bytes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Drive using the given reactor handle.
|
||||
pub fn handle(mut self, handle: reactor::Handle) -> Self {
|
||||
self.reactor = Some(Reactor::Handle(handle));
|
||||
self
|
||||
}
|
||||
|
||||
/// Drive using the given reactor remote.
|
||||
pub fn remote(mut self, remote: reactor::Remote) -> Self {
|
||||
self.reactor = Some(Reactor::Remote(remote));
|
||||
self
|
||||
}
|
||||
|
||||
/// Connect using the given `Context`
|
||||
#[cfg(feature = "tls")]
|
||||
pub fn tls(mut self, tls_ctx: Context) -> Self {
|
||||
self.tls_ctx = Some(tls_ctx);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
enum Reactor {
|
||||
Handle(reactor::Handle),
|
||||
Remote(reactor::Remote),
|
||||
}
|
||||
|
||||
impl fmt::Debug for Reactor {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
const HANDLE: &str = "Reactor::Handle";
|
||||
const HANDLE_INNER: &str = "Handle { .. }";
|
||||
const REMOTE: &str = "Reactor::Remote";
|
||||
const REMOTE_INNER: &str = "Remote { .. }";
|
||||
|
||||
match *self {
|
||||
Reactor::Handle(_) => f.debug_tuple(HANDLE).field(&HANDLE_INNER).finish(),
|
||||
Reactor::Remote(_) => f.debug_tuple(REMOTE).field(&REMOTE_INNER).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct Client<Req, Resp, E>
|
||||
where
|
||||
Req: Serialize + 'static,
|
||||
Resp: DeserializeOwned + 'static,
|
||||
E: DeserializeOwned + 'static,
|
||||
{
|
||||
inner: ClientService<StreamType, Proto<Req, Response<Resp, E>>>,
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> Clone for Client<Req, Resp, E>
|
||||
where
|
||||
Req: Serialize + 'static,
|
||||
Resp: DeserializeOwned + 'static,
|
||||
E: DeserializeOwned + 'static,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Client {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> Service for Client<Req, Resp, E>
|
||||
where
|
||||
Req: Serialize + Send + 'static,
|
||||
Resp: DeserializeOwned + Send + 'static,
|
||||
E: DeserializeOwned + Send + 'static,
|
||||
{
|
||||
type Request = Req;
|
||||
type Response = Resp;
|
||||
type Error = ::Error<E>;
|
||||
type Future = ResponseFuture<Req, Resp, E>;
|
||||
|
||||
fn call(&self, request: Self::Request) -> Self::Future {
|
||||
fn identity<T>(t: T) -> T {
|
||||
t
|
||||
}
|
||||
self.inner
|
||||
.call(request)
|
||||
.map(Self::map_err as _)
|
||||
.map_err(::Error::from as _)
|
||||
.and_then(identity as _)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> Client<Req, Resp, E>
|
||||
where
|
||||
Req: Serialize + 'static,
|
||||
Resp: DeserializeOwned + 'static,
|
||||
E: DeserializeOwned + 'static,
|
||||
{
|
||||
fn bind(handle: &reactor::Handle, tcp: StreamType, max_payload_size: u64) -> Self
|
||||
where
|
||||
Req: Serialize + Send + 'static,
|
||||
Resp: DeserializeOwned + Send + 'static,
|
||||
E: DeserializeOwned + Send + 'static,
|
||||
{
|
||||
let inner = Proto::new(max_payload_size).bind_client(handle, tcp);
|
||||
Client { inner }
|
||||
}
|
||||
|
||||
fn map_err(resp: WireResponse<Resp, E>) -> Result<Resp, ::Error<E>> {
|
||||
resp.map(|r| r.map_err(::Error::from))
|
||||
.map_err(::Error::ResponseDeserialize)
|
||||
.and_then(|r| r)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> fmt::Debug for Client<Req, Resp, E>
|
||||
where
|
||||
Req: Serialize + 'static,
|
||||
Resp: DeserializeOwned + 'static,
|
||||
E: DeserializeOwned + 'static,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
write!(f, "Client {{ .. }}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension methods for clients.
|
||||
pub trait ClientExt: Sized {
|
||||
/// The type of the future returned when calling `connect`.
|
||||
type ConnectFut: Future<Item = Self, Error = io::Error>;
|
||||
|
||||
/// Connects to a server located at the given address, using the given options.
|
||||
fn connect(addr: SocketAddr, options: Options) -> Self::ConnectFut;
|
||||
}
|
||||
|
||||
/// A future that resolves to a `Client` or an `io::Error`.
|
||||
pub type ConnectFuture<Req, Resp, E> = futures::Flatten<
|
||||
futures::MapErr<
|
||||
futures::Oneshot<io::Result<Client<Req, Resp, E>>>,
|
||||
fn(futures::Canceled) -> io::Error,
|
||||
>,
|
||||
>;
|
||||
|
||||
impl<Req, Resp, E> ClientExt for Client<Req, Resp, E>
|
||||
where
|
||||
Req: Serialize + Send + 'static,
|
||||
Resp: DeserializeOwned + Send + 'static,
|
||||
E: DeserializeOwned + Send + 'static,
|
||||
{
|
||||
type ConnectFut = ConnectFuture<Req, Resp, E>;
|
||||
|
||||
fn connect(addr: SocketAddr, options: Options) -> Self::ConnectFut {
|
||||
// we need to do this for tls because we need to avoid moving the entire `Options`
|
||||
// struct into the `setup` closure, since `Reactor` is not `Send`.
|
||||
#[cfg(feature = "tls")]
|
||||
let mut options = options;
|
||||
#[cfg(feature = "tls")]
|
||||
let tls_ctx = options.tls_ctx.take();
|
||||
|
||||
let max_payload_size = options.max_payload_size;
|
||||
|
||||
let connect = move |handle: &reactor::Handle| {
|
||||
let handle2 = handle.clone();
|
||||
TcpStream::connect(&addr, handle)
|
||||
.and_then(move |socket| {
|
||||
// TODO(https://github.com/tokio-rs/tokio-proto/issues/132): move this into the
|
||||
// ServerProto impl
|
||||
#[cfg(feature = "tls")]
|
||||
match tls_ctx {
|
||||
Some(tls_ctx) => {
|
||||
future::Either::A(
|
||||
tls_ctx
|
||||
.tls_connector
|
||||
.connect_async(&tls_ctx.domain, socket)
|
||||
.map(StreamType::Tls)
|
||||
.map_err(native_to_io),
|
||||
)
|
||||
}
|
||||
None => future::Either::B(future::ok(StreamType::Tcp(socket))),
|
||||
}
|
||||
#[cfg(not(feature = "tls"))] future::ok(StreamType::Tcp(socket))
|
||||
})
|
||||
.map(move |tcp| Client::bind(&handle2, tcp, max_payload_size))
|
||||
};
|
||||
let (tx, rx) = futures::oneshot();
|
||||
let setup = move |handle: &reactor::Handle| {
|
||||
connect(handle).then(move |result| {
|
||||
// If send fails it means the client no longer cared about connecting.
|
||||
let _ = tx.send(result);
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
|
||||
match options.reactor {
|
||||
Some(Reactor::Handle(handle)) => {
|
||||
handle.spawn(setup(&handle));
|
||||
}
|
||||
Some(Reactor::Remote(remote)) => {
|
||||
remote.spawn(setup);
|
||||
}
|
||||
None => {
|
||||
REMOTE.spawn(setup);
|
||||
}
|
||||
}
|
||||
fn panic(canceled: futures::Canceled) -> io::Error {
|
||||
unreachable!(canceled)
|
||||
}
|
||||
rx.map_err(panic as _).flatten()
|
||||
}
|
||||
}
|
||||
|
||||
type ResponseFuture<Req, Resp, E> =
|
||||
futures::AndThen<futures::MapErr<
|
||||
futures::Map<<ClientService<StreamType, Proto<Req, Response<Resp, E>>> as Service>::Future,
|
||||
fn(WireResponse<Resp, E>) -> Result<Resp, ::Error<E>>>,
|
||||
fn(io::Error) -> ::Error<E>>,
|
||||
Result<Resp, ::Error<E>>,
|
||||
fn(Result<Resp, ::Error<E>>) -> Result<Resp, ::Error<E>>>;
|
||||
|
||||
type WireResponse<R, E> = Result<Response<R, E>, bincode::Error>;
|
||||
@@ -1,4 +0,0 @@
|
||||
/// Provides the base client stubs used by the service macro.
|
||||
pub mod client;
|
||||
/// Provides the base server boilerplate used by service implementations.
|
||||
pub mod server;
|
||||
@@ -1,76 +0,0 @@
|
||||
use futures::unsync;
|
||||
use std::io;
|
||||
use tokio_service::{NewService, Service};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Action {
|
||||
Increment,
|
||||
Decrement,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Tracker {
|
||||
pub tx: unsync::mpsc::UnboundedSender<Action>,
|
||||
}
|
||||
|
||||
impl Tracker {
|
||||
pub fn pair() -> (Self, unsync::mpsc::UnboundedReceiver<Action>) {
|
||||
let (tx, rx) = unsync::mpsc::unbounded();
|
||||
(Self { tx }, rx)
|
||||
}
|
||||
|
||||
pub fn increment(&self) {
|
||||
let _ = self.tx.unbounded_send(Action::Increment);
|
||||
}
|
||||
|
||||
pub fn decrement(&self) {
|
||||
debug!("Closing connection");
|
||||
let _ = self.tx.unbounded_send(Action::Decrement);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TrackingService<S> {
|
||||
pub service: S,
|
||||
pub tracker: Tracker,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TrackingNewService<S> {
|
||||
pub new_service: S,
|
||||
pub connection_tracker: Tracker,
|
||||
}
|
||||
|
||||
impl<S: Service> Service for TrackingService<S> {
|
||||
type Request = S::Request;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn call(&self, req: Self::Request) -> Self::Future {
|
||||
trace!("Calling service.");
|
||||
self.service.call(req)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Drop for TrackingService<S> {
|
||||
fn drop(&mut self) {
|
||||
debug!("Dropping ConnnectionTrackingService.");
|
||||
self.tracker.decrement();
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: NewService> NewService for TrackingNewService<S> {
|
||||
type Request = S::Request;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Instance = TrackingService<S::Instance>;
|
||||
|
||||
fn new_service(&self) -> io::Result<Self::Instance> {
|
||||
self.connection_tracker.increment();
|
||||
Ok(TrackingService {
|
||||
service: self.new_service.new_service()?,
|
||||
tracker: self.connection_tracker.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,471 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
use {bincode, net2};
|
||||
use errors::WireError;
|
||||
use futures::{Async, Future, Poll, Stream, future as futures};
|
||||
use protocol::Proto;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use stream_type::StreamType;
|
||||
use tokio_core::net::{Incoming, TcpListener, TcpStream};
|
||||
use tokio_core::reactor;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_proto::BindServer;
|
||||
use tokio_service::NewService;
|
||||
|
||||
mod connection;
|
||||
mod shutdown;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "tls")] {
|
||||
use native_tls::{self, TlsAcceptor};
|
||||
use tokio_tls::{AcceptAsync, TlsAcceptorExt, TlsStream};
|
||||
use errors::native_to_io;
|
||||
} else {}
|
||||
}
|
||||
|
||||
pub use self::shutdown::{Shutdown, ShutdownFuture};
|
||||
|
||||
/// A handle to a bound server.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Handle {
|
||||
addr: SocketAddr,
|
||||
shutdown: Shutdown,
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
/// Returns a hook for shutting down the server.
|
||||
pub fn shutdown(&self) -> &Shutdown {
|
||||
&self.shutdown
|
||||
}
|
||||
|
||||
/// The socket address the server is bound to.
|
||||
pub fn addr(&self) -> SocketAddr {
|
||||
self.addr
|
||||
}
|
||||
}
|
||||
|
||||
enum Acceptor {
|
||||
Tcp,
|
||||
#[cfg(feature = "tls")]
|
||||
Tls(TlsAcceptor),
|
||||
}
|
||||
|
||||
struct Accept {
|
||||
#[cfg(feature = "tls")]
|
||||
inner: futures::Either<
|
||||
futures::MapErr<
|
||||
futures::Map<AcceptAsync<TcpStream>, fn(TlsStream<TcpStream>) -> StreamType>,
|
||||
fn(native_tls::Error) -> io::Error,
|
||||
>,
|
||||
futures::FutureResult<StreamType, io::Error>,
|
||||
>,
|
||||
#[cfg(not(feature = "tls"))]
|
||||
inner: futures::FutureResult<StreamType, io::Error>,
|
||||
}
|
||||
|
||||
impl Future for Accept {
|
||||
type Item = StreamType;
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
self.inner.poll()
|
||||
}
|
||||
}
|
||||
|
||||
impl Acceptor {
|
||||
// TODO(https://github.com/tokio-rs/tokio-proto/issues/132): move this into the ServerProto impl
|
||||
#[cfg(feature = "tls")]
|
||||
fn accept(&self, socket: TcpStream) -> Accept {
|
||||
Accept {
|
||||
inner: match *self {
|
||||
Acceptor::Tls(ref tls_acceptor) => {
|
||||
futures::Either::A(
|
||||
tls_acceptor
|
||||
.accept_async(socket)
|
||||
.map(StreamType::Tls as _)
|
||||
.map_err(native_to_io),
|
||||
)
|
||||
}
|
||||
Acceptor::Tcp => futures::Either::B(futures::ok(StreamType::Tcp(socket))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tls"))]
|
||||
fn accept(&self, socket: TcpStream) -> Accept {
|
||||
Accept {
|
||||
inner: futures::ok(StreamType::Tcp(socket)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
impl From<Options> for Acceptor {
|
||||
fn from(options: Options) -> Self {
|
||||
match options.tls_acceptor {
|
||||
Some(tls_acceptor) => Acceptor::Tls(tls_acceptor),
|
||||
None => Acceptor::Tcp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tls"))]
|
||||
impl From<Options> for Acceptor {
|
||||
fn from(_: Options) -> Self {
|
||||
Acceptor::Tcp
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Acceptor {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Acceptor::*;
|
||||
#[cfg(feature = "tls")]
|
||||
const TLS: &str = "TlsAcceptor { .. }";
|
||||
|
||||
match *self {
|
||||
Tcp => fmt.debug_tuple("Acceptor::Tcp").finish(),
|
||||
#[cfg(feature = "tls")]
|
||||
Tls(_) => fmt.debug_tuple("Acceptor::Tls").field(&TLS).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Accept {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("Accept").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AcceptStream<S> {
|
||||
stream: S,
|
||||
acceptor: Acceptor,
|
||||
future: Option<Accept>,
|
||||
}
|
||||
|
||||
impl<S> Stream for AcceptStream<S>
|
||||
where
|
||||
S: Stream<Item = (TcpStream, SocketAddr), Error = io::Error>,
|
||||
{
|
||||
type Item = <Accept as Future>::Item;
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, io::Error> {
|
||||
if self.future.is_none() {
|
||||
let stream = match try_ready!(self.stream.poll()) {
|
||||
None => return Ok(Async::Ready(None)),
|
||||
Some((stream, _)) => stream,
|
||||
};
|
||||
self.future = Some(self.acceptor.accept(stream));
|
||||
}
|
||||
assert!(self.future.is_some());
|
||||
match self.future.as_mut().unwrap().poll() {
|
||||
Ok(Async::Ready(e)) => {
|
||||
self.future = None;
|
||||
Ok(Async::Ready(Some(e)))
|
||||
}
|
||||
Err(e) => {
|
||||
self.future = None;
|
||||
Err(e)
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional options to configure how the server operates.
|
||||
pub struct Options {
|
||||
/// Max packet size in bytes.
|
||||
max_payload_size: u64,
|
||||
#[cfg(feature = "tls")]
|
||||
tls_acceptor: Option<TlsAcceptor>,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
#[cfg(not(feature = "tls"))]
|
||||
fn default() -> Self {
|
||||
Options {
|
||||
max_payload_size: 2 << 20,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
fn default() -> Self {
|
||||
Options {
|
||||
max_payload_size: 2 << 20,
|
||||
tls_acceptor: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
/// Set the max payload size in bytes. The default is 2 << 20 (2 MiB).
|
||||
pub fn max_payload_size(mut self, bytes: u64) -> Self {
|
||||
self.max_payload_size = bytes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `TlsAcceptor`
|
||||
#[cfg(feature = "tls")]
|
||||
pub fn tls(mut self, tls_acceptor: TlsAcceptor) -> Self {
|
||||
self.tls_acceptor = Some(tls_acceptor);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Options {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[cfg(feature = "tls")]
|
||||
const SOME: &str = "Some(_)";
|
||||
#[cfg(feature = "tls")]
|
||||
const NONE: &str = "None";
|
||||
|
||||
let mut debug_struct = fmt.debug_struct("Options");
|
||||
#[cfg(feature = "tls")]
|
||||
debug_struct.field(
|
||||
"tls_acceptor",
|
||||
if self.tls_acceptor.is_some() {
|
||||
&SOME
|
||||
} else {
|
||||
&NONE
|
||||
},
|
||||
);
|
||||
debug_struct.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A message from server to client.
|
||||
#[doc(hidden)]
|
||||
pub type Response<T, E> = Result<T, WireError<E>>;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn listen<S, Req, Resp, E>(new_service: S,
|
||||
addr: SocketAddr,
|
||||
handle: &reactor::Handle,
|
||||
options: Options)
|
||||
-> io::Result<(Handle, Listen<S, Req, Resp, E>)>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: DeserializeOwned + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static
|
||||
{
|
||||
let (addr, shutdown, server) = listen_with(
|
||||
new_service,
|
||||
addr,
|
||||
handle,
|
||||
options.max_payload_size,
|
||||
Acceptor::from(options),
|
||||
)?;
|
||||
Ok((
|
||||
Handle {
|
||||
addr: addr,
|
||||
shutdown: shutdown,
|
||||
},
|
||||
server,
|
||||
))
|
||||
}
|
||||
|
||||
/// Spawns a service that binds to the given address using the given handle.
|
||||
fn listen_with<S, Req, Resp, E>(new_service: S,
|
||||
addr: SocketAddr,
|
||||
handle: &reactor::Handle,
|
||||
max_payload_size: u64,
|
||||
acceptor: Acceptor)
|
||||
-> io::Result<(SocketAddr, Shutdown, Listen<S, Req, Resp, E>)>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: DeserializeOwned + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static
|
||||
{
|
||||
let listener = listener(&addr, handle)?;
|
||||
let addr = listener.local_addr()?;
|
||||
debug!("Listening on {}.", addr);
|
||||
|
||||
let handle = handle.clone();
|
||||
let (connection_tracker, shutdown, shutdown_future) = shutdown::Watcher::triple();
|
||||
let server = BindStream {
|
||||
handle: handle,
|
||||
new_service: connection::TrackingNewService {
|
||||
connection_tracker: connection_tracker,
|
||||
new_service: new_service,
|
||||
},
|
||||
stream: AcceptStream {
|
||||
stream: listener.incoming(),
|
||||
acceptor: acceptor,
|
||||
future: None,
|
||||
},
|
||||
max_payload_size: max_payload_size,
|
||||
};
|
||||
|
||||
let server = AlwaysOkUnit(server.select(shutdown_future));
|
||||
Ok((addr, shutdown, Listen { inner: server }))
|
||||
}
|
||||
|
||||
fn listener(addr: &SocketAddr, handle: &reactor::Handle) -> io::Result<TcpListener> {
|
||||
const PENDING_CONNECTION_BACKLOG: i32 = 1024;
|
||||
|
||||
let builder = match *addr {
|
||||
SocketAddr::V4(_) => net2::TcpBuilder::new_v4(),
|
||||
SocketAddr::V6(_) => net2::TcpBuilder::new_v6(),
|
||||
}?;
|
||||
configure_tcp(&builder)?;
|
||||
builder.reuse_address(true)?;
|
||||
builder
|
||||
.bind(addr)?
|
||||
.listen(PENDING_CONNECTION_BACKLOG)
|
||||
.and_then(|l| TcpListener::from_listener(l, addr, handle))
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn configure_tcp(tcp: &net2::TcpBuilder) -> io::Result<()> {
|
||||
use net2::unix::UnixTcpBuilderExt;
|
||||
tcp.reuse_port(true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn configure_tcp(_tcp: &net2::TcpBuilder) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct BindStream<S, St> {
|
||||
handle: reactor::Handle,
|
||||
new_service: connection::TrackingNewService<S>,
|
||||
stream: St,
|
||||
max_payload_size: u64,
|
||||
}
|
||||
|
||||
impl<S, St> fmt::Debug for BindStream<S, St>
|
||||
where
|
||||
S: fmt::Debug,
|
||||
St: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("BindStream")
|
||||
.field("handle", &self.handle)
|
||||
.field("new_service", &self.new_service)
|
||||
.field("stream", &self.stream)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req, Resp, E, I, St> BindStream<S, St>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: DeserializeOwned + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static,
|
||||
I: AsyncRead + AsyncWrite + 'static,
|
||||
St: Stream<Item = I, Error = io::Error>
|
||||
{
|
||||
fn bind_each(&mut self) -> Poll<(), io::Error> {
|
||||
loop {
|
||||
match try!(self.stream.poll()) {
|
||||
Async::Ready(Some(socket)) => {
|
||||
Proto::new(self.max_payload_size).bind_server(&self.handle,
|
||||
socket,
|
||||
self.new_service.new_service()?);
|
||||
}
|
||||
Async::Ready(None) => return Ok(Async::Ready(())),
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req, Resp, E, I, St> Future for BindStream<S, St>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: DeserializeOwned + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static,
|
||||
I: AsyncRead + AsyncWrite + 'static,
|
||||
St: Stream<Item = I, Error = io::Error>
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.bind_each() {
|
||||
Ok(Async::Ready(())) => Ok(Async::Ready(())),
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(e) => {
|
||||
error!("While processing incoming connections: {}", e);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The future representing a running server.
|
||||
#[doc(hidden)]
|
||||
pub struct Listen<S, Req, Resp, E>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: DeserializeOwned + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static
|
||||
{
|
||||
inner: AlwaysOkUnit<futures::Select<BindStream<S, AcceptStream<Incoming>>, shutdown::Watcher>>,
|
||||
}
|
||||
|
||||
impl<S, Req, Resp, E> Future for Listen<S, Req, Resp, E>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: DeserializeOwned + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
self.inner.poll()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req, Resp, E> fmt::Debug for Listen<S, Req, Resp, E>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
Req: DeserializeOwned + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("Listen").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AlwaysOkUnit<F>(F);
|
||||
|
||||
impl<F> Future for AlwaysOkUnit<F>
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
match self.0.poll() {
|
||||
Ok(Async::Ready(_)) | Err(_) => Ok(Async::Ready(())),
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
|
||||
|
||||
use super::{AlwaysOkUnit, connection};
|
||||
use futures::{Async, Future, Poll, Stream, future as futures, stream};
|
||||
use futures::sync::{mpsc, oneshot};
|
||||
use futures::unsync;
|
||||
|
||||
/// A hook to shut down a running server.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Shutdown {
|
||||
tx: mpsc::UnboundedSender<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
/// A future that resolves when server shutdown completes.
|
||||
#[derive(Debug)]
|
||||
pub struct ShutdownFuture {
|
||||
inner: futures::Either<futures::FutureResult<(), ()>, AlwaysOkUnit<oneshot::Receiver<()>>>,
|
||||
}
|
||||
|
||||
impl Future for ShutdownFuture {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
self.inner.poll()
|
||||
}
|
||||
}
|
||||
|
||||
impl Shutdown {
|
||||
/// Initiates an orderly server shutdown.
|
||||
///
|
||||
/// First, the server enters lameduck mode, in which
|
||||
/// existing connections are honored but no new connections are accepted. Then, once all
|
||||
/// connections are closed, it initates total shutdown.
|
||||
///
|
||||
/// The returned future resolves when the server is completely shut down.
|
||||
pub fn shutdown(&self) -> ShutdownFuture {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let inner = if self.tx.unbounded_send(tx).is_err() {
|
||||
trace!("Server already initiated shutdown.");
|
||||
futures::Either::A(futures::ok(()))
|
||||
} else {
|
||||
futures::Either::B(AlwaysOkUnit(rx))
|
||||
};
|
||||
ShutdownFuture { inner: inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Watcher {
|
||||
shutdown_rx: stream::Take<mpsc::UnboundedReceiver<oneshot::Sender<()>>>,
|
||||
connections: unsync::mpsc::UnboundedReceiver<connection::Action>,
|
||||
queued_error: Option<()>,
|
||||
shutdown: Option<oneshot::Sender<()>>,
|
||||
done: bool,
|
||||
num_connections: u64,
|
||||
}
|
||||
|
||||
impl Watcher {
|
||||
pub fn triple() -> (connection::Tracker, Shutdown, Self) {
|
||||
let (connection_tx, connections) = connection::Tracker::pair();
|
||||
let (shutdown_tx, shutdown_rx) = mpsc::unbounded();
|
||||
(
|
||||
connection_tx,
|
||||
Shutdown { tx: shutdown_tx },
|
||||
Watcher {
|
||||
shutdown_rx: shutdown_rx.take(1),
|
||||
connections: connections,
|
||||
queued_error: None,
|
||||
shutdown: None,
|
||||
done: false,
|
||||
num_connections: 0,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn process_connection(&mut self, action: connection::Action) {
|
||||
match action {
|
||||
connection::Action::Increment => self.num_connections += 1,
|
||||
connection::Action::Decrement => self.num_connections -= 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_shutdown_requests(&mut self) -> Poll<Option<()>, ()> {
|
||||
Ok(Async::Ready(match try_ready!(self.shutdown_rx.poll()) {
|
||||
Some(tx) => {
|
||||
debug!("Received shutdown request.");
|
||||
self.shutdown = Some(tx);
|
||||
Some(())
|
||||
}
|
||||
None => None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn poll_connections(&mut self) -> Poll<Option<()>, ()> {
|
||||
Ok(Async::Ready(match try_ready!(self.connections.poll()) {
|
||||
Some(action) => {
|
||||
self.process_connection(action);
|
||||
Some(())
|
||||
}
|
||||
None => None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn poll_shutdown_requests_and_connections(&mut self) -> Poll<Option<()>, ()> {
|
||||
if let Some(e) = self.queued_error.take() {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
match try!(self.poll_shutdown_requests()) {
|
||||
Async::NotReady => {
|
||||
match try_ready!(self.poll_connections()) {
|
||||
Some(()) => Ok(Async::Ready(Some(()))),
|
||||
None => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
Async::Ready(None) => {
|
||||
match try_ready!(self.poll_connections()) {
|
||||
Some(()) => Ok(Async::Ready(Some(()))),
|
||||
None => Ok(Async::Ready(None)),
|
||||
}
|
||||
}
|
||||
Async::Ready(Some(())) => {
|
||||
match self.poll_connections() {
|
||||
Err(e) => {
|
||||
self.queued_error = Some(e);
|
||||
Ok(Async::Ready(Some(())))
|
||||
}
|
||||
Ok(Async::NotReady) | Ok(Async::Ready(None)) | Ok(Async::Ready(Some(()))) => {
|
||||
Ok(Async::Ready(Some(())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn should_continue(&mut self) -> bool {
|
||||
match self.shutdown.take() {
|
||||
Some(shutdown) => {
|
||||
debug!("Lameduck mode: {} open connections", self.num_connections);
|
||||
if self.num_connections == 0 {
|
||||
debug!("Shutting down.");
|
||||
// Not required for the shutdown future to be waited on, so this
|
||||
// can fail (which is fine).
|
||||
let _ = shutdown.send(());
|
||||
false
|
||||
} else {
|
||||
self.shutdown = Some(shutdown);
|
||||
true
|
||||
}
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn process_request(&mut self) -> Poll<Option<()>, ()> {
|
||||
if self.done {
|
||||
return Ok(Async::Ready(None));
|
||||
}
|
||||
if self.should_continue() {
|
||||
self.poll_shutdown_requests_and_connections()
|
||||
} else {
|
||||
self.done = true;
|
||||
Ok(Async::Ready(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Watcher {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
loop {
|
||||
match try!(self.process_request()) {
|
||||
Async::Ready(Some(())) => continue,
|
||||
Async::Ready(None) => return Ok(Async::Ready(())),
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
213
src/lib.rs
213
src/lib.rs
@@ -1,213 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
//! tarpc is an RPC framework for rust with a focus on ease of use. Defining a
|
||||
//! service can be done in just a few lines of code, and most of the boilerplate of
|
||||
//! writing a server is taken care of for you.
|
||||
//!
|
||||
//! ## What is an RPC framework?
|
||||
//! "RPC" stands for "Remote Procedure Call," a function call where the work of
|
||||
//! producing the return value is being done somewhere else. When an rpc function is
|
||||
//! invoked, behind the scenes the function contacts some other process somewhere
|
||||
//! and asks them to evaluate the function instead. The original function then
|
||||
//! returns the value produced by the other process.
|
||||
//!
|
||||
//! RPC frameworks are a fundamental building block of most microservices-oriented
|
||||
//! architectures. Two well-known ones are [gRPC](http://www.grpc.io) and
|
||||
//! [Cap'n Proto](https://capnproto.org/).
|
||||
//!
|
||||
//! tarpc differentiates itself from other RPC frameworks by defining the schema in code,
|
||||
//! rather than in a separate language such as .proto. This means there's no separate compilation
|
||||
//! process, and no cognitive context switching between different languages. Additionally, it
|
||||
//! works with the community-backed library serde: any serde-serializable type can be used as
|
||||
//! arguments to tarpc fns.
|
||||
//!
|
||||
//! Example usage:
|
||||
//!
|
||||
//! ```
|
||||
//! #![feature(plugin, use_extern_macros, proc_macro_path_invoc)]
|
||||
//! #![plugin(tarpc_plugins)]
|
||||
//!
|
||||
//! #[macro_use]
|
||||
//! extern crate tarpc;
|
||||
//! extern crate tokio_core;
|
||||
//!
|
||||
//! use tarpc::sync::{client, server};
|
||||
//! use tarpc::sync::client::ClientExt;
|
||||
//! use tarpc::util::Never;
|
||||
//! use tokio_core::reactor;
|
||||
//! use std::sync::mpsc;
|
||||
//! use std::thread;
|
||||
//!
|
||||
//! service! {
|
||||
//! rpc hello(name: String) -> String;
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Clone)]
|
||||
//! struct HelloServer;
|
||||
//!
|
||||
//! impl SyncService for HelloServer {
|
||||
//! fn hello(&self, name: String) -> Result<String, Never> {
|
||||
//! Ok(format!("Hello, {}!", name))
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let (tx, rx) = mpsc::channel();
|
||||
//! thread::spawn(move || {
|
||||
//! let mut handle = HelloServer.listen("localhost:10000",
|
||||
//! server::Options::default()).unwrap();
|
||||
//! tx.send(handle.addr()).unwrap();
|
||||
//! handle.run();
|
||||
//! });
|
||||
//! let addr = rx.recv().unwrap();
|
||||
//! let client = SyncClient::connect(addr, client::Options::default()).unwrap();
|
||||
//! println!("{}", client.hello("Mom".to_string()).unwrap());
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Example usage with TLS:
|
||||
//!
|
||||
//! ```no-run
|
||||
//! #![feature(plugin, use_extern_macros, proc_macro_path_invoc)]
|
||||
//! #![plugin(tarpc_plugins)]
|
||||
//!
|
||||
//! #[macro_use]
|
||||
//! extern crate tarpc;
|
||||
//!
|
||||
//! use tarpc::sync::{client, server};
|
||||
//! use tarpc::sync::client::ClientExt;
|
||||
//! use tarpc::tls;
|
||||
//! use tarpc::util::Never;
|
||||
//! use tarpc::native_tls::{TlsAcceptor, Pkcs12};
|
||||
//!
|
||||
//! service! {
|
||||
//! rpc hello(name: String) -> String;
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Clone)]
|
||||
//! struct HelloServer;
|
||||
//!
|
||||
//! impl SyncService for HelloServer {
|
||||
//! fn hello(&self, name: String) -> Result<String, Never> {
|
||||
//! Ok(format!("Hello, {}!", name))
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn get_acceptor() -> TlsAcceptor {
|
||||
//! let buf = include_bytes!("test/identity.p12");
|
||||
//! let pkcs12 = Pkcs12::from_der(buf, "password").unwrap();
|
||||
//! TlsAcceptor::builder(pkcs12).unwrap().build().unwrap()
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let addr = "localhost:10000";
|
||||
//! let acceptor = get_acceptor();
|
||||
//! let _server = HelloServer.listen(addr, server::Options::default().tls(acceptor));
|
||||
//! let client = SyncClient::connect(addr,
|
||||
//! client::Options::default()
|
||||
//! .tls(tls::client::Context::new("foobar.com").unwrap()))
|
||||
//! .unwrap();
|
||||
//! println!("{}", client.hello("Mom".to_string()).unwrap());
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![deny(missing_docs, missing_debug_implementations)]
|
||||
#![feature(never_type)]
|
||||
#![cfg_attr(test, feature(plugin, use_extern_macros, proc_macro_path_invoc))]
|
||||
#![cfg_attr(test, plugin(tarpc_plugins))]
|
||||
|
||||
extern crate byteorder;
|
||||
extern crate bytes;
|
||||
#[macro_use]
|
||||
extern crate cfg_if;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate net2;
|
||||
extern crate num_cpus;
|
||||
extern crate thread_pool;
|
||||
extern crate tokio_codec;
|
||||
extern crate tokio_io;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub extern crate bincode;
|
||||
#[doc(hidden)]
|
||||
#[macro_use]
|
||||
pub extern crate futures;
|
||||
#[doc(hidden)]
|
||||
pub extern crate serde;
|
||||
#[doc(hidden)]
|
||||
#[macro_use]
|
||||
pub extern crate serde_derive;
|
||||
#[doc(hidden)]
|
||||
pub extern crate tokio_core;
|
||||
#[doc(hidden)]
|
||||
pub extern crate tokio_proto;
|
||||
#[doc(hidden)]
|
||||
pub extern crate tokio_service;
|
||||
|
||||
pub use errors::Error;
|
||||
#[doc(hidden)]
|
||||
pub use errors::WireError;
|
||||
|
||||
/// Provides some utility error types, as well as a trait for spawning futures on the default event
|
||||
/// loop.
|
||||
pub mod util;
|
||||
|
||||
/// Provides the macro used for constructing rpc services and client stubs.
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
/// Synchronous version of the tarpc API
|
||||
pub mod sync;
|
||||
/// Futures-based version of the tarpc API.
|
||||
pub mod future;
|
||||
/// TLS-specific functionality.
|
||||
#[cfg(feature = "tls")]
|
||||
pub mod tls;
|
||||
/// Provides implementations of `ClientProto` and `ServerProto` that implement the tarpc protocol.
|
||||
/// The tarpc protocol is a length-delimited, bincode-serialized payload.
|
||||
mod protocol;
|
||||
/// Provides a few different error types.
|
||||
mod errors;
|
||||
/// Provides an abstraction over TLS and TCP streams.
|
||||
mod stream_type;
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tokio_core::reactor;
|
||||
|
||||
lazy_static! {
|
||||
/// The `Remote` for the default reactor core.
|
||||
static ref REMOTE: reactor::Remote = {
|
||||
spawn_core()
|
||||
};
|
||||
}
|
||||
|
||||
/// Spawns a `reactor::Core` running forever on a new thread.
|
||||
fn spawn_core() -> reactor::Remote {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let mut core = reactor::Core::new().unwrap();
|
||||
tx.send(core.handle().remote().clone()).unwrap();
|
||||
|
||||
// Run forever
|
||||
core.run(futures::empty::<(), !>()).unwrap();
|
||||
});
|
||||
rx.recv().unwrap()
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "tls")] {
|
||||
extern crate tokio_tls;
|
||||
extern crate native_tls as native_tls_inner;
|
||||
|
||||
/// Re-exported TLS-related types from the `native_tls` crate.
|
||||
pub mod native_tls {
|
||||
pub use native_tls_inner::{Error, Pkcs12, TlsAcceptor, TlsConnector};
|
||||
}
|
||||
} else {}
|
||||
}
|
||||
1191
src/macros.rs
1191
src/macros.rs
File diff suppressed because it is too large
Load Diff
@@ -1,196 +0,0 @@
|
||||
#![feature(plugin_registrar, rustc_private)]
|
||||
|
||||
extern crate itertools;
|
||||
extern crate rustc_plugin;
|
||||
extern crate syntax;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rustc_plugin::Registry;
|
||||
use syntax::ast::{self, Ident, TraitRef, Ty, TyKind};
|
||||
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager};
|
||||
use syntax::codemap::Span;
|
||||
use syntax::parse::{self, token, str_lit, PResult};
|
||||
use syntax::parse::parser::{Parser, PathStyle};
|
||||
use syntax::symbol::Symbol;
|
||||
use syntax::ptr::P;
|
||||
use syntax::tokenstream::{TokenTree, TokenStream};
|
||||
use syntax::util::small_vector::SmallVector;
|
||||
|
||||
fn snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
|
||||
let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.into());
|
||||
// The `expand_expr` method is called so that any macro calls in the
|
||||
// parsed expression are expanded.
|
||||
|
||||
let mut item = match parser.parse_trait_item(&mut false) {
|
||||
Ok(s) => s,
|
||||
Err(mut diagnostic) => {
|
||||
diagnostic.emit();
|
||||
return DummyResult::any(sp);
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(mut diagnostic) = parser.expect(&token::Eof) {
|
||||
diagnostic.emit();
|
||||
return DummyResult::any(sp);
|
||||
}
|
||||
|
||||
let old_ident = convert(&mut item.ident);
|
||||
|
||||
// As far as I know, it's not possible in macro_rules! to reference an $ident in a doc string,
|
||||
// so this is the hacky workaround.
|
||||
//
|
||||
// This code looks intimidating, but it's just iterating through the trait item's attributes
|
||||
// copying non-doc attributes, and modifying doc attributes such that replacing any {} in the
|
||||
// doc string instead holds the original, snake_case ident.
|
||||
let attrs: Vec<_> = item.attrs
|
||||
.drain(..)
|
||||
.map(|mut attr| {
|
||||
if !attr.is_sugared_doc {
|
||||
return attr;
|
||||
}
|
||||
|
||||
// Getting at the underlying doc comment is surprisingly painful.
|
||||
// The call-chain goes something like:
|
||||
//
|
||||
// - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/attr.rs#L283
|
||||
// - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/attr.rs#L1067
|
||||
// - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/attr.rs#L1196
|
||||
// - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/parse/mod.rs#L399
|
||||
// - https://github.com/rust-lang/rust/blob/9c15de4fd59bee290848b5443c7e194fd5afb02c/src/libsyntax/parse/mod.rs#L268
|
||||
//
|
||||
// Note that a docstring (i.e., something with is_sugared_doc) *always* has exactly two
|
||||
// tokens: an Eq followed by a Literal, where the Literal contains a Str_. We therefore
|
||||
// match against that, modifying the inner Str with our modified Symbol.
|
||||
let mut tokens = attr.tokens.clone().into_trees();
|
||||
if let Some(tt @ TokenTree::Token(_, token::Eq)) = tokens.next() {
|
||||
let mut docstr = tokens.next().expect("Docstrings must have literal docstring");
|
||||
if let TokenTree::Token(_, token::Literal(token::Str_(ref mut doc), _)) = docstr {
|
||||
*doc = Symbol::intern(&str_lit(&doc.as_str(), None).replace("{}", &old_ident));
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
attr.tokens = TokenStream::concat(vec![tt.into(), docstr.into()]);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
attr
|
||||
})
|
||||
.collect();
|
||||
item.attrs.extend(attrs.into_iter());
|
||||
|
||||
MacEager::trait_items(SmallVector::one(item))
|
||||
}
|
||||
|
||||
fn impl_snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
|
||||
let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.into());
|
||||
// The `expand_expr` method is called so that any macro calls in the
|
||||
// parsed expression are expanded.
|
||||
|
||||
let mut item = match parser.parse_impl_item(&mut false) {
|
||||
Ok(s) => s,
|
||||
Err(mut diagnostic) => {
|
||||
diagnostic.emit();
|
||||
return DummyResult::any(sp);
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(mut diagnostic) = parser.expect(&token::Eof) {
|
||||
diagnostic.emit();
|
||||
return DummyResult::any(sp);
|
||||
}
|
||||
|
||||
convert(&mut item.ident);
|
||||
MacEager::impl_items(SmallVector::one(item))
|
||||
}
|
||||
|
||||
fn ty_snake_to_camel(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
|
||||
let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.into());
|
||||
// The `expand_expr` method is called so that any macro calls in the
|
||||
// parsed expression are expanded.
|
||||
|
||||
let mut path = match parser.parse_path(PathStyle::Type) {
|
||||
Ok(s) => s,
|
||||
Err(mut diagnostic) => {
|
||||
diagnostic.emit();
|
||||
return DummyResult::any(sp);
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(mut diagnostic) = parser.expect(&token::Eof) {
|
||||
diagnostic.emit();
|
||||
return DummyResult::any(sp);
|
||||
}
|
||||
|
||||
// Only capitalize the final segment
|
||||
convert(&mut path.segments
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.ident);
|
||||
MacEager::ty(P(Ty {
|
||||
id: ast::DUMMY_NODE_ID,
|
||||
node: TyKind::Path(None, path),
|
||||
span: sp,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Converts an ident in-place to CamelCase and returns the previous ident.
|
||||
fn convert(ident: &mut Ident) -> String {
|
||||
let ident_str = ident.to_string();
|
||||
let mut camel_ty = String::new();
|
||||
|
||||
{
|
||||
// Find the first non-underscore and add it capitalized.
|
||||
let mut chars = ident_str.chars();
|
||||
|
||||
// Find the first non-underscore char, uppercase it, and append it.
|
||||
// Guaranteed to succeed because all idents must have at least one non-underscore char.
|
||||
camel_ty.extend(chars.find(|&c| c != '_').unwrap().to_uppercase());
|
||||
|
||||
// When we find an underscore, we remove it and capitalize the next char. To do this,
|
||||
// we need to ensure the next char is not another underscore.
|
||||
let mut chars = chars.coalesce(|c1, c2| {
|
||||
if c1 == '_' && c2 == '_' {
|
||||
Ok(c1)
|
||||
} else {
|
||||
Err((c1, c2))
|
||||
}
|
||||
});
|
||||
|
||||
while let Some(c) = chars.next() {
|
||||
if c != '_' {
|
||||
camel_ty.push(c);
|
||||
} else if let Some(c) = chars.next() {
|
||||
camel_ty.extend(c.to_uppercase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The Fut suffix is hardcoded right now; this macro isn't really meant to be general-purpose.
|
||||
camel_ty.push_str("Fut");
|
||||
|
||||
*ident = Ident::with_empty_ctxt(Symbol::intern(&camel_ty));
|
||||
ident_str
|
||||
}
|
||||
|
||||
trait ParseTraitRef {
|
||||
fn parse_trait_ref(&mut self) -> PResult<TraitRef>;
|
||||
}
|
||||
|
||||
impl<'a> ParseTraitRef for Parser<'a> {
|
||||
/// Parse a::B<String,i32>
|
||||
fn parse_trait_ref(&mut self) -> PResult<TraitRef> {
|
||||
Ok(TraitRef {
|
||||
path: self.parse_path(PathStyle::Type)?,
|
||||
ref_id: ast::DUMMY_NODE_ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[plugin_registrar]
|
||||
#[doc(hidden)]
|
||||
pub fn plugin_registrar(reg: &mut Registry) {
|
||||
reg.register_macro("snake_to_camel", snake_to_camel);
|
||||
reg.register_macro("impl_snake_to_camel", impl_snake_to_camel);
|
||||
reg.register_macro("ty_snake_to_camel", ty_snake_to_camel);
|
||||
}
|
||||
248
src/protocol.rs
248
src/protocol.rs
@@ -1,248 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
use bincode;
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use bytes::BytesMut;
|
||||
use bytes::buf::BufMut;
|
||||
use serde;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_codec::{Encoder, Decoder, Framed};
|
||||
use tokio_proto::multiplex::{ClientProto, ServerProto};
|
||||
use tokio_proto::streaming::multiplex::RequestId;
|
||||
|
||||
// `Encode` is the type that `Codec` encodes. `Decode` is the type it decodes.
|
||||
#[derive(Debug)]
|
||||
pub struct Codec<Encode, Decode> {
|
||||
max_payload_size: u64,
|
||||
state: CodecState,
|
||||
_phantom_data: PhantomData<(Encode, Decode)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CodecState {
|
||||
Id,
|
||||
Len { id: u64 },
|
||||
Payload { id: u64, len: u64 },
|
||||
}
|
||||
|
||||
impl<Encode, Decode> Codec<Encode, Decode> {
|
||||
fn new(max_payload_size: u64) -> Self {
|
||||
Codec {
|
||||
max_payload_size,
|
||||
state: CodecState::Id,
|
||||
_phantom_data: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn too_big(payload_size: u64, max_payload_size: u64) -> io::Error {
|
||||
warn!(
|
||||
"Not sending too-big packet of size {} (max is {})",
|
||||
payload_size,
|
||||
max_payload_size
|
||||
);
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"Maximum payload size is {} bytes but got a payload of {}",
|
||||
max_payload_size,
|
||||
payload_size
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
impl<Encode, Decode> Encoder for Codec<Encode, Decode>
|
||||
where
|
||||
Encode: serde::Serialize,
|
||||
Decode: serde::de::DeserializeOwned,
|
||||
{
|
||||
type Item = (RequestId, Encode);
|
||||
type Error = io::Error;
|
||||
|
||||
fn encode(&mut self, (id, message): Self::Item, buf: &mut BytesMut) -> io::Result<()> {
|
||||
let payload_size = bincode::serialized_size(&message).map_err(|serialize_err| {
|
||||
io::Error::new(io::ErrorKind::Other, serialize_err)
|
||||
})?;
|
||||
if payload_size > self.max_payload_size {
|
||||
return Err(too_big(payload_size, self.max_payload_size));
|
||||
}
|
||||
let message_size = 2 * mem::size_of::<u64>() + payload_size as usize;
|
||||
buf.reserve(message_size);
|
||||
buf.put_u64_be(id);
|
||||
trace!("Encoded request id = {} as {:?}", id, buf);
|
||||
buf.put_u64_be(payload_size);
|
||||
bincode::serialize_into(&mut buf.writer(), &message)
|
||||
.map_err(|serialize_err| {
|
||||
io::Error::new(io::ErrorKind::Other, serialize_err)
|
||||
})?;
|
||||
trace!("Encoded buffer: {:?}", buf);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Encode, Decode> Decoder for Codec<Encode, Decode>
|
||||
where
|
||||
Decode: serde::de::DeserializeOwned,
|
||||
{
|
||||
type Item = (RequestId, Result<Decode, bincode::Error>);
|
||||
type Error = io::Error;
|
||||
|
||||
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<Self::Item>> {
|
||||
use self::CodecState::*;
|
||||
trace!("Codec::decode: {:?}", buf);
|
||||
|
||||
loop {
|
||||
match self.state {
|
||||
Id if buf.len() < mem::size_of::<u64>() => {
|
||||
trace!("--> Buf len is {}; waiting for 8 to parse id.", buf.len());
|
||||
return Ok(None);
|
||||
}
|
||||
Id => {
|
||||
let mut id_buf = buf.split_to(mem::size_of::<u64>());
|
||||
let id = BigEndian::read_u64(&*id_buf);
|
||||
trace!("--> Parsed id = {} from {:?}", id, id_buf);
|
||||
self.state = Len { id };
|
||||
}
|
||||
Len { .. } if buf.len() < mem::size_of::<u64>() => {
|
||||
trace!(
|
||||
"--> Buf len is {}; waiting for 8 to parse packet length.",
|
||||
buf.len()
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
Len { id } => {
|
||||
let len_buf = buf.split_to(mem::size_of::<u64>());
|
||||
let len = BigEndian::read_u64(&*len_buf);
|
||||
trace!(
|
||||
"--> Parsed payload length = {}, remaining buffer length = {}",
|
||||
len,
|
||||
buf.len()
|
||||
);
|
||||
if len > self.max_payload_size {
|
||||
return Err(too_big(len, self.max_payload_size));
|
||||
}
|
||||
self.state = Payload { id, len };
|
||||
}
|
||||
Payload { len, .. } if buf.len() < len as usize => {
|
||||
trace!(
|
||||
"--> Buf len is {}; waiting for {} to parse payload.",
|
||||
buf.len(),
|
||||
len
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
Payload { id, len } => {
|
||||
let payload = buf.split_to(len as usize);
|
||||
let result = bincode::deserialize(&payload);
|
||||
// Reset the state machine because, either way, we're done processing this
|
||||
// message.
|
||||
self.state = Id;
|
||||
|
||||
return Ok(Some((id, result)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the `multiplex::ServerProto` trait.
|
||||
#[derive(Debug)]
|
||||
pub struct Proto<Encode, Decode> {
|
||||
max_payload_size: u64,
|
||||
_phantom_data: PhantomData<(Encode, Decode)>,
|
||||
}
|
||||
|
||||
impl<Encode, Decode> Proto<Encode, Decode> {
|
||||
/// Returns a new `Proto`.
|
||||
pub fn new(max_payload_size: u64) -> Self {
|
||||
Proto {
|
||||
max_payload_size: max_payload_size,
|
||||
_phantom_data: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Encode, Decode> ServerProto<T> for Proto<Encode, Decode>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
Encode: serde::Serialize + 'static,
|
||||
Decode: serde::de::DeserializeOwned + 'static,
|
||||
{
|
||||
type Response = Encode;
|
||||
type Request = Result<Decode, bincode::Error>;
|
||||
type Transport = Framed<T, Codec<Encode, Decode>>;
|
||||
type BindTransport = Result<Self::Transport, io::Error>;
|
||||
|
||||
fn bind_transport(&self, io: T) -> Self::BindTransport {
|
||||
Ok(Framed::new(io, Codec::new(self.max_payload_size)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Encode, Decode> ClientProto<T> for Proto<Encode, Decode>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
Encode: serde::Serialize + 'static,
|
||||
Decode: serde::de::DeserializeOwned + 'static,
|
||||
{
|
||||
type Response = Result<Decode, bincode::Error>;
|
||||
type Request = Encode;
|
||||
type Transport = Framed<T, Codec<Encode, Decode>>;
|
||||
type BindTransport = Result<Self::Transport, io::Error>;
|
||||
|
||||
fn bind_transport(&self, io: T) -> Self::BindTransport {
|
||||
Ok(Framed::new(io, Codec::new(self.max_payload_size)))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize() {
|
||||
const MSG: (u64, (char, char, char)) = (4, ('a', 'b', 'c'));
|
||||
let mut buf = BytesMut::with_capacity(10);
|
||||
|
||||
// Serialize twice to check for idempotence.
|
||||
for _ in 0..2 {
|
||||
let mut codec: Codec<(char, char, char), (char, char, char)> = Codec::new(2_000_000);
|
||||
codec.encode(MSG, &mut buf).unwrap();
|
||||
let actual: Result<
|
||||
Option<(u64, Result<(char, char, char), bincode::Error>)>,
|
||||
io::Error,
|
||||
> = codec.decode(&mut buf);
|
||||
|
||||
match actual {
|
||||
Ok(Some((id, ref v))) if id == MSG.0 && *v.as_ref().unwrap() == MSG.1 => {}
|
||||
bad => panic!("Expected {:?}, but got {:?}", Some(MSG), bad),
|
||||
}
|
||||
|
||||
assert!(buf.is_empty(), "Expected empty buf but got {:?}", buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_big() {
|
||||
let mut codec: Codec<Vec<u8>, Vec<u8>> = Codec::new(24);
|
||||
|
||||
let mut buf = BytesMut::with_capacity(40);
|
||||
assert_eq!(
|
||||
codec
|
||||
.encode((0, vec![0; 24]), &mut buf)
|
||||
.err()
|
||||
.unwrap()
|
||||
.kind(),
|
||||
io::ErrorKind::InvalidData
|
||||
);
|
||||
|
||||
// Header
|
||||
buf.put_slice(&mut [0u8; 8]);
|
||||
// Len
|
||||
buf.put_slice(&mut [0u8, 0, 0, 0, 0, 0, 0, 25]);
|
||||
assert_eq!(
|
||||
codec.decode(&mut buf).err().unwrap().kind(),
|
||||
io::ErrorKind::InvalidData
|
||||
);
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
use bytes::{Buf, BufMut};
|
||||
use futures::Poll;
|
||||
use std::io;
|
||||
use tokio_core::net::TcpStream;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
#[cfg(feature = "tls")]
|
||||
use tokio_tls::TlsStream;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StreamType {
|
||||
Tcp(TcpStream),
|
||||
#[cfg(feature = "tls")]
|
||||
Tls(TlsStream<TcpStream>),
|
||||
}
|
||||
|
||||
impl From<TcpStream> for StreamType {
|
||||
fn from(stream: TcpStream) -> Self {
|
||||
StreamType::Tcp(stream)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
impl From<TlsStream<TcpStream>> for StreamType {
|
||||
fn from(stream: TlsStream<TcpStream>) -> Self {
|
||||
StreamType::Tls(stream)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for StreamType {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match *self {
|
||||
StreamType::Tcp(ref mut stream) => stream.read(buf),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref mut stream) => stream.read(buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for StreamType {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
match *self {
|
||||
StreamType::Tcp(ref mut stream) => stream.write(buf),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref mut stream) => stream.write(buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
match *self {
|
||||
StreamType::Tcp(ref mut stream) => stream.flush(),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref mut stream) => stream.flush(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for StreamType {
|
||||
// By overriding this fn, `StreamType` is obliged to never read the uninitialized buffer.
|
||||
// Most sane implementations would never have a reason to, and `StreamType` does not, so
|
||||
// this is safe.
|
||||
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
|
||||
match *self {
|
||||
StreamType::Tcp(ref stream) => stream.prepare_uninitialized_buffer(buf),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref stream) => stream.prepare_uninitialized_buffer(buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
|
||||
match *self {
|
||||
StreamType::Tcp(ref mut stream) => stream.read_buf(buf),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref mut stream) => stream.read_buf(buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for StreamType {
|
||||
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||
match *self {
|
||||
StreamType::Tcp(ref mut stream) => stream.shutdown(),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref mut stream) => stream.shutdown(),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
|
||||
match *self {
|
||||
StreamType::Tcp(ref mut stream) => stream.write_buf(buf),
|
||||
#[cfg(feature = "tls")]
|
||||
StreamType::Tls(ref mut stream) => stream.write_buf(buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
use future::client::{Client as FutureClient, ClientExt as FutureClientExt,
|
||||
Options as FutureOptions};
|
||||
use futures::{Future, Stream};
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
#[cfg(feature = "tls")]
|
||||
use tls::client::Context;
|
||||
use tokio_core::reactor;
|
||||
use tokio_proto::util::client_proxy::{ClientProxy, Receiver, pair};
|
||||
use tokio_service::Service;
|
||||
use util::FirstSocketAddr;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct Client<Req, Resp, E> {
|
||||
proxy: ClientProxy<Req, Resp, ::Error<E>>,
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> Clone for Client<Req, Resp, E> {
|
||||
fn clone(&self) -> Self {
|
||||
Client {
|
||||
proxy: self.proxy.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> fmt::Debug for Client<Req, Resp, E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
const PROXY: &str = "ClientProxy { .. }";
|
||||
f.debug_struct("Client").field("proxy", &PROXY).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> Client<Req, Resp, E>
|
||||
where
|
||||
Req: Serialize + Send + 'static,
|
||||
Resp: DeserializeOwned + Send + 'static,
|
||||
E: DeserializeOwned + Send + 'static,
|
||||
{
|
||||
/// Drives an RPC call for the given request.
|
||||
pub fn call(&self, request: Req) -> Result<Resp, ::Error<E>> {
|
||||
// Must call wait here to block on the response.
|
||||
// The request handler relies on this fact to safely unwrap the
|
||||
// oneshot send.
|
||||
self.proxy.call(request).wait()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional options to configure how the client connects and operates.
|
||||
pub struct Options {
|
||||
/// Max packet size in bytes.
|
||||
max_payload_size: u64,
|
||||
#[cfg(feature = "tls")]
|
||||
tls_ctx: Option<Context>,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
#[cfg(not(feature = "tls"))]
|
||||
fn default() -> Self {
|
||||
Options {
|
||||
max_payload_size: 2_000_000,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
fn default() -> Self {
|
||||
Options {
|
||||
max_payload_size: 2_000_000,
|
||||
tls_ctx: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
/// Set the max payload size in bytes. The default is 2,000,000 (2 MB).
|
||||
pub fn max_payload_size(mut self, bytes: u64) -> Self {
|
||||
self.max_payload_size = bytes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Connect using the given `Context`
|
||||
#[cfg(feature = "tls")]
|
||||
pub fn tls(mut self, ctx: Context) -> Self {
|
||||
self.tls_ctx = Some(ctx);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Options {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
#[cfg(feature = "tls")]
|
||||
const SOME: &str = "Some(_)";
|
||||
#[cfg(feature = "tls")]
|
||||
const NONE: &str = "None";
|
||||
let mut f = f.debug_struct("Options");
|
||||
#[cfg(feature = "tls")] f.field("tls_ctx", if self.tls_ctx.is_some() { &SOME } else { &NONE });
|
||||
f.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<FutureOptions> for (reactor::Handle, Options) {
|
||||
#[cfg(feature = "tls")]
|
||||
fn into(self) -> FutureOptions {
|
||||
let (handle, options) = self;
|
||||
let mut opts = FutureOptions::default().handle(handle);
|
||||
if let Some(tls_ctx) = options.tls_ctx {
|
||||
opts = opts.tls(tls_ctx);
|
||||
}
|
||||
opts
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tls"))]
|
||||
fn into(self) -> FutureOptions {
|
||||
let (handle, _) = self;
|
||||
FutureOptions::default().handle(handle)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension methods for Clients.
|
||||
pub trait ClientExt: Sized {
|
||||
/// Connects to a server located at the given address.
|
||||
fn connect<A>(addr: A, options: Options) -> io::Result<Self>
|
||||
where
|
||||
A: ToSocketAddrs;
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> ClientExt for Client<Req, Resp, E>
|
||||
where
|
||||
Req: Serialize + Send + 'static,
|
||||
Resp: DeserializeOwned + Send + 'static,
|
||||
E: DeserializeOwned + Send + 'static,
|
||||
{
|
||||
fn connect<A>(addr: A, options: Options) -> io::Result<Self>
|
||||
where
|
||||
A: ToSocketAddrs,
|
||||
{
|
||||
let addr = addr.try_first_socket_addr()?;
|
||||
let (connect_tx, connect_rx) = mpsc::channel();
|
||||
thread::spawn(move || match RequestHandler::connect(addr, options) {
|
||||
Ok((proxy, mut handler)) => {
|
||||
connect_tx.send(Ok(proxy)).unwrap();
|
||||
handler.handle_requests();
|
||||
}
|
||||
Err(e) => connect_tx.send(Err(e)).unwrap(),
|
||||
});
|
||||
Ok(connect_rx.recv().unwrap()?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Forwards incoming requests of type `Req`
|
||||
/// with expected response `Result<Resp, ::Error<E>>`
|
||||
/// to service `S`.
|
||||
struct RequestHandler<Req, Resp, E, S> {
|
||||
reactor: reactor::Core,
|
||||
client: S,
|
||||
requests: Receiver<Req, Resp, ::Error<E>>,
|
||||
}
|
||||
|
||||
impl<Req, Resp, E> RequestHandler<Req, Resp, E, FutureClient<Req, Resp, E>>
|
||||
where
|
||||
Req: Serialize + Send + 'static,
|
||||
Resp: DeserializeOwned + Send + 'static,
|
||||
E: DeserializeOwned + Send + 'static,
|
||||
{
|
||||
/// Creates a new `RequestHandler` by connecting a `FutureClient` to the given address
|
||||
/// using the given options.
|
||||
fn connect(addr: SocketAddr, options: Options) -> io::Result<(Client<Req, Resp, E>, Self)> {
|
||||
let mut reactor = reactor::Core::new()?;
|
||||
let options = (reactor.handle(), options).into();
|
||||
let client = reactor.run(FutureClient::connect(addr, options))?;
|
||||
let (proxy, requests) = pair();
|
||||
Ok((
|
||||
Client { proxy },
|
||||
RequestHandler {
|
||||
reactor,
|
||||
client,
|
||||
requests,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Resp, E, S> RequestHandler<Req, Resp, E, S>
|
||||
where
|
||||
Req: Serialize + 'static,
|
||||
Resp: DeserializeOwned + 'static,
|
||||
E: DeserializeOwned + 'static,
|
||||
S: Service<Request = Req, Response = Resp, Error = ::Error<E>>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
fn handle_requests(&mut self) {
|
||||
let RequestHandler {
|
||||
ref mut reactor,
|
||||
ref mut requests,
|
||||
ref mut client,
|
||||
} = *self;
|
||||
let handle = reactor.handle();
|
||||
let requests = requests
|
||||
.map(|result| {
|
||||
match result {
|
||||
Ok(req) => req,
|
||||
// The ClientProxy never sends Err currently
|
||||
Err(e) => panic!("Unimplemented error handling in RequestHandler: {}", e),
|
||||
}
|
||||
})
|
||||
.for_each(|(request, response_tx)| {
|
||||
let request = client.call(request).then(move |response| {
|
||||
// Safe to unwrap because clients always block on the response future.
|
||||
response_tx
|
||||
.send(response)
|
||||
.map_err(|_| ())
|
||||
.expect("Client should block on response");
|
||||
Ok(())
|
||||
});
|
||||
handle.spawn(request);
|
||||
Ok(())
|
||||
});
|
||||
reactor.run(requests).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle_requests() {
|
||||
use futures::future;
|
||||
|
||||
struct Client;
|
||||
impl Service for Client {
|
||||
type Request = i32;
|
||||
type Response = i32;
|
||||
type Error = ::Error<()>;
|
||||
type Future = future::FutureResult<i32, ::Error<()>>;
|
||||
|
||||
fn call(&self, req: i32) -> Self::Future {
|
||||
future::ok(req)
|
||||
}
|
||||
}
|
||||
|
||||
let (request, requests) = ::futures::sync::mpsc::unbounded();
|
||||
let reactor = reactor::Core::new().unwrap();
|
||||
let client = Client;
|
||||
let mut request_handler = RequestHandler {
|
||||
reactor,
|
||||
client,
|
||||
requests,
|
||||
};
|
||||
// Test that `handle_requests` returns when all request senders are dropped.
|
||||
drop(request);
|
||||
request_handler.handle_requests();
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/// Provides the base client stubs used by the service macro.
|
||||
pub mod client;
|
||||
/// Provides the base server boilerplate used by service implementations.
|
||||
pub mod server;
|
||||
@@ -1,249 +0,0 @@
|
||||
use {bincode, future, num_cpus};
|
||||
use future::server::{Response, Shutdown};
|
||||
use futures::{Future, future as futures};
|
||||
use futures::sync::oneshot;
|
||||
#[cfg(feature = "tls")]
|
||||
use native_tls_inner::TlsAcceptor;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
use std::usize;
|
||||
use thread_pool::{self, Sender, Task, ThreadPool};
|
||||
use tokio_core::reactor;
|
||||
use tokio_service::{NewService, Service};
|
||||
|
||||
/// Additional options to configure how the server operates.
|
||||
#[derive(Debug)]
|
||||
pub struct Options {
|
||||
thread_pool: thread_pool::Builder,
|
||||
opts: future::server::Options,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
fn default() -> Self {
|
||||
let num_cpus = num_cpus::get();
|
||||
Options {
|
||||
thread_pool: thread_pool::Builder::new()
|
||||
.keep_alive(Duration::from_secs(60))
|
||||
.max_pool_size(num_cpus * 100)
|
||||
.core_pool_size(num_cpus)
|
||||
.work_queue_capacity(usize::MAX)
|
||||
.name_prefix("request-thread-"),
|
||||
opts: future::server::Options::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
/// Set the max payload size in bytes. The default is 2,000,000 (2 MB).
|
||||
pub fn max_payload_size(mut self, bytes: u64) -> Self {
|
||||
self.opts = self.opts.max_payload_size(bytes);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the thread pool builder to use when creating the server's thread pool.
|
||||
pub fn thread_pool(mut self, builder: thread_pool::Builder) -> Self {
|
||||
self.thread_pool = builder;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the `TlsAcceptor`
|
||||
#[cfg(feature = "tls")]
|
||||
pub fn tls(mut self, tls_acceptor: TlsAcceptor) -> Self {
|
||||
self.opts = self.opts.tls(tls_acceptor);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a bound server. Must be run to start serving requests.
|
||||
#[must_use = "A server does nothing until `run` is called."]
|
||||
pub struct Handle {
|
||||
reactor: reactor::Core,
|
||||
handle: future::server::Handle,
|
||||
server: Box<Future<Item = (), Error = ()>>,
|
||||
}
|
||||
|
||||
impl Handle {
|
||||
/// Runs the server on the current thread, blocking indefinitely.
|
||||
pub fn run(mut self) {
|
||||
trace!("Running...");
|
||||
match self.reactor.run(self.server) {
|
||||
Ok(()) => debug!("Server successfully shutdown."),
|
||||
Err(()) => debug!("Server shutdown due to error."),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a hook for shutting down the server.
|
||||
pub fn shutdown(&self) -> Shutdown {
|
||||
self.handle.shutdown().clone()
|
||||
}
|
||||
|
||||
/// The socket address the server is bound to.
|
||||
pub fn addr(&self) -> SocketAddr {
|
||||
self.handle.addr()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Handle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
const SERVER: &str = "Box<Future<Item = (), Error = ()>>";
|
||||
|
||||
f.debug_struct("Handle")
|
||||
.field("reactor", &self.reactor)
|
||||
.field("handle", &self.handle)
|
||||
.field("server", &SERVER)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn listen<S, Req, Resp, E>(new_service: S,
|
||||
addr: SocketAddr,
|
||||
options: Options)
|
||||
-> io::Result<Handle>
|
||||
where S: NewService<Request = Result<Req, bincode::Error>,
|
||||
Response = Response<Resp, E>,
|
||||
Error = io::Error> + 'static,
|
||||
<S::Instance as Service>::Future: Send + 'static,
|
||||
S::Response: Send,
|
||||
S::Error: Send,
|
||||
Req: DeserializeOwned + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
E: Serialize + 'static
|
||||
{
|
||||
let new_service = NewThreadService::new(new_service, options.thread_pool);
|
||||
let reactor = reactor::Core::new()?;
|
||||
let (handle, server) =
|
||||
future::server::listen(new_service, addr, &reactor.handle(), options.opts)?;
|
||||
let server = Box::new(server);
|
||||
Ok(Handle {
|
||||
reactor: reactor,
|
||||
handle: handle,
|
||||
server: server,
|
||||
})
|
||||
}
|
||||
|
||||
/// A service that uses a thread pool.
|
||||
struct NewThreadService<S>
|
||||
where
|
||||
S: NewService,
|
||||
{
|
||||
new_service: S,
|
||||
sender: Sender<ServiceTask<<S::Instance as Service>::Future>>,
|
||||
_pool: ThreadPool<ServiceTask<<S::Instance as Service>::Future>>,
|
||||
}
|
||||
|
||||
/// A service that runs by executing request handlers in a thread pool.
|
||||
struct ThreadService<S>
|
||||
where
|
||||
S: Service,
|
||||
{
|
||||
service: S,
|
||||
sender: Sender<ServiceTask<S::Future>>,
|
||||
}
|
||||
|
||||
/// A task that handles a single request.
|
||||
struct ServiceTask<F>
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
future: F,
|
||||
tx: oneshot::Sender<Result<F::Item, F::Error>>,
|
||||
}
|
||||
|
||||
impl<S> NewThreadService<S>
|
||||
where
|
||||
S: NewService,
|
||||
<S::Instance as Service>::Future: Send + 'static,
|
||||
S::Response: Send,
|
||||
S::Error: Send,
|
||||
{
|
||||
/// Create a NewThreadService by wrapping another service.
|
||||
fn new(new_service: S, pool: thread_pool::Builder) -> Self {
|
||||
let (sender, _pool) = pool.build();
|
||||
NewThreadService {
|
||||
new_service,
|
||||
sender,
|
||||
_pool,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> NewService for NewThreadService<S>
|
||||
where
|
||||
S: NewService,
|
||||
<S::Instance as Service>::Future: Send + 'static,
|
||||
S::Response: Send,
|
||||
S::Error: Send,
|
||||
{
|
||||
type Request = S::Request;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Instance = ThreadService<S::Instance>;
|
||||
|
||||
fn new_service(&self) -> io::Result<Self::Instance> {
|
||||
Ok(ThreadService {
|
||||
service: self.new_service.new_service()?,
|
||||
sender: self.sender.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Task for ServiceTask<F>
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Item: Send,
|
||||
F::Error: Send,
|
||||
{
|
||||
fn run(self) {
|
||||
// Don't care if sending fails. It just means the request is no longer
|
||||
// being handled (I think).
|
||||
let _ = self.tx.send(self.future.wait());
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Service for ThreadService<S>
|
||||
where
|
||||
S: Service,
|
||||
S::Future: Send + 'static,
|
||||
S::Response: Send,
|
||||
S::Error: Send,
|
||||
{
|
||||
type Request = S::Request;
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = futures::AndThen<
|
||||
futures::MapErr<
|
||||
oneshot::Receiver<Result<Self::Response, Self::Error>>,
|
||||
fn(oneshot::Canceled) -> Self::Error,
|
||||
>,
|
||||
Result<Self::Response, Self::Error>,
|
||||
fn(Result<Self::Response, Self::Error>)
|
||||
-> Result<Self::Response, Self::Error>,
|
||||
>;
|
||||
|
||||
fn call(&self, request: Self::Request) -> Self::Future {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.sender
|
||||
.send(ServiceTask {
|
||||
future: self.service.call(request),
|
||||
tx: tx,
|
||||
})
|
||||
.unwrap();
|
||||
rx.map_err(unreachable as _).and_then(ident)
|
||||
}
|
||||
}
|
||||
|
||||
fn unreachable<T, U>(t: T) -> U
|
||||
where
|
||||
T: fmt::Display,
|
||||
{
|
||||
unreachable!(t)
|
||||
}
|
||||
|
||||
fn ident<T>(t: T) -> T {
|
||||
t
|
||||
}
|
||||
50
src/tls.rs
50
src/tls.rs
@@ -1,50 +0,0 @@
|
||||
/// TLS-specific functionality for clients.
|
||||
pub mod client {
|
||||
use native_tls::{Error, TlsConnector};
|
||||
use std::fmt;
|
||||
|
||||
/// TLS context for client
|
||||
pub struct Context {
|
||||
/// Domain to connect to
|
||||
pub domain: String,
|
||||
/// TLS connector
|
||||
pub tls_connector: TlsConnector,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Try to construct a new `Context`.
|
||||
///
|
||||
/// The provided domain will be used for both
|
||||
/// [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) and certificate hostname
|
||||
/// validation.
|
||||
pub fn new<S: Into<String>>(domain: S) -> Result<Self, Error> {
|
||||
Ok(Context {
|
||||
domain: domain.into(),
|
||||
tls_connector: TlsConnector::builder()?.build()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct a new `Context` using the provided domain and `TlsConnector`
|
||||
///
|
||||
/// The domain will be used for both
|
||||
/// [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) and certificate hostname
|
||||
/// validation.
|
||||
pub fn from_connector<S: Into<String>>(domain: S, tls_connector: TlsConnector) -> Self {
|
||||
Context {
|
||||
domain: domain.into(),
|
||||
tls_connector: tls_connector,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Context {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
const TLS_CONNECTOR: &str = "TlsConnector { .. }";
|
||||
f.debug_struct("Context")
|
||||
.field("domain", &self.domain)
|
||||
.field("tls_connector", &TLS_CONNECTOR)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
185
src/util.rs
185
src/util.rs
@@ -1,185 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the MIT License, <LICENSE or http://opensource.org/licenses/MIT>.
|
||||
// This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
use futures::{Future, IntoFuture, Poll};
|
||||
use futures::stream::Stream;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::{fmt, io, mem};
|
||||
use std::error::Error;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
|
||||
/// A bottom type that impls `Error`, `Serialize`, and `Deserialize`. It is impossible to
|
||||
/// instantiate this type.
|
||||
#[allow(unreachable_code)]
|
||||
pub struct Never(!);
|
||||
|
||||
impl fmt::Debug for Never {
|
||||
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for Never {
|
||||
fn description(&self) -> &str {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Never {
|
||||
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Never {
|
||||
type Item = Never;
|
||||
type Error = Never;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for Never {
|
||||
type Item = Never;
|
||||
type Error = Never;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Never {
|
||||
fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
// Please don't try to deserialize this. :(
|
||||
impl<'a> Deserialize<'a> for Never {
|
||||
fn deserialize<D>(_: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'a>,
|
||||
{
|
||||
panic!("Never cannot be instantiated!");
|
||||
}
|
||||
}
|
||||
|
||||
/// A `String` that impls `std::error::Error`. Useful for quick-and-dirty error propagation.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Message(pub String);
|
||||
|
||||
impl Error for Message {
|
||||
fn description(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Message {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Into<String>> From<S> for Message {
|
||||
fn from(s: S) -> Self {
|
||||
Message(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Provides a utility method for more ergonomically parsing a `SocketAddr` when only one is
|
||||
/// needed.
|
||||
pub trait FirstSocketAddr: ToSocketAddrs {
|
||||
/// Returns the first resolved `SocketAddr`, if one exists.
|
||||
fn try_first_socket_addr(&self) -> io::Result<SocketAddr> {
|
||||
if let Some(a) = self.to_socket_addrs()?.next() {
|
||||
Ok(a)
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::AddrNotAvailable,
|
||||
"`ToSocketAddrs::to_socket_addrs` returned an empty iterator.",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the first resolved `SocketAddr` or panics otherwise.
|
||||
fn first_socket_addr(&self) -> SocketAddr {
|
||||
self.try_first_socket_addr().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: ToSocketAddrs> FirstSocketAddr for A {}
|
||||
|
||||
/// Creates a new future which will eventually be the same as the one created
|
||||
/// by calling the closure provided with the arguments provided.
|
||||
///
|
||||
/// The provided closure is only run once the future has a callback scheduled
|
||||
/// on it, otherwise the callback never runs. Once run, however, this future is
|
||||
/// the same as the one the closure creates.
|
||||
pub fn lazy<F, A, R>(f: F, args: A) -> Lazy<F, A, R>
|
||||
where
|
||||
F: FnOnce(A) -> R,
|
||||
R: IntoFuture,
|
||||
{
|
||||
Lazy {
|
||||
inner: _Lazy::First(f, args),
|
||||
}
|
||||
}
|
||||
|
||||
/// A future which defers creation of the actual future until a callback is
|
||||
/// scheduled.
|
||||
///
|
||||
/// This is created by the `lazy` function.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct Lazy<F, A, R: IntoFuture> {
|
||||
inner: _Lazy<F, A, R::Future>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum _Lazy<F, A, R> {
|
||||
First(F, A),
|
||||
Second(R),
|
||||
Moved,
|
||||
}
|
||||
|
||||
impl<F, A, R> Lazy<F, A, R>
|
||||
where
|
||||
F: FnOnce(A) -> R,
|
||||
R: IntoFuture,
|
||||
{
|
||||
fn get(&mut self) -> &mut R::Future {
|
||||
match self.inner {
|
||||
_Lazy::First(..) => {}
|
||||
_Lazy::Second(ref mut f) => return f,
|
||||
_Lazy::Moved => panic!(), // can only happen if `f()` panics
|
||||
}
|
||||
match mem::replace(&mut self.inner, _Lazy::Moved) {
|
||||
_Lazy::First(f, args) => self.inner = _Lazy::Second(f(args).into_future()),
|
||||
_ => panic!(), // we already found First
|
||||
}
|
||||
match self.inner {
|
||||
_Lazy::Second(ref mut f) => f,
|
||||
_ => panic!(), // we just stored Second
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, A, R> Future for Lazy<F, A, R>
|
||||
where
|
||||
F: FnOnce(A) -> R,
|
||||
R: IntoFuture,
|
||||
{
|
||||
type Item = R::Item;
|
||||
type Error = R::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<R::Item, R::Error> {
|
||||
self.get().poll()
|
||||
}
|
||||
}
|
||||
42
tarpc/Cargo.toml
Normal file
42
tarpc/Cargo.toml
Normal file
@@ -0,0 +1,42 @@
|
||||
cargo-features = ["rename-dependency"]
|
||||
|
||||
[package]
|
||||
name = "tarpc"
|
||||
version = "0.14.0"
|
||||
authors = ["Adam Wright <adam.austin.wright@gmail.com>", "Tim Kuehn <timothy.j.kuehn@gmail.com>"]
|
||||
edition = "2018"
|
||||
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"
|
||||
description = "An RPC framework for Rust with a focus on ease of use."
|
||||
|
||||
[features]
|
||||
serde1 = ["rpc/serde1", "serde", "serde/derive"]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "google/tarpc" }
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
serde = { optional = true, version = "1.0" }
|
||||
tarpc-plugins = { path = "../plugins", version = "0.5.0" }
|
||||
rpc = { package = "tarpc-lib", path = "../rpc", version = "0.2" }
|
||||
|
||||
[target.'cfg(not(test))'.dependencies]
|
||||
futures-preview = "0.3.0-alpha.9"
|
||||
|
||||
[dev-dependencies]
|
||||
bincode = "1.0"
|
||||
bytes = { version = "0.4", features = ["serde"] }
|
||||
humantime = "1.0"
|
||||
futures-preview = { version = "0.3.0-alpha.9", features = ["compat", "tokio-compat"] }
|
||||
bincode-transport = { package = "tarpc-bincode-transport", version = "0.3", path = "../bincode-transport" }
|
||||
env_logger = "0.5"
|
||||
tokio = "0.1"
|
||||
tokio-executor = "0.1"
|
||||
tokio-tcp = "0.1"
|
||||
pin-utils = "0.1.0-alpha.3"
|
||||
185
tarpc/examples/pubsub.rs
Normal file
185
tarpc/examples/pubsub.rs
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
#![feature(
|
||||
arbitrary_self_types,
|
||||
pin,
|
||||
futures_api,
|
||||
await_macro,
|
||||
async_await,
|
||||
existential_type,
|
||||
proc_macro_hygiene
|
||||
)]
|
||||
|
||||
use futures::{
|
||||
future::{self, Ready},
|
||||
prelude::*,
|
||||
Future,
|
||||
};
|
||||
use rpc::{
|
||||
client, context,
|
||||
server::{self, Handler, Server},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io,
|
||||
net::SocketAddr,
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub mod subscriber {
|
||||
tarpc::service! {
|
||||
rpc receive(message: String);
|
||||
}
|
||||
}
|
||||
|
||||
pub mod publisher {
|
||||
use std::net::SocketAddr;
|
||||
tarpc::service! {
|
||||
rpc broadcast(message: String);
|
||||
rpc subscribe(id: u32, address: SocketAddr) -> Result<(), String>;
|
||||
rpc unsubscribe(id: u32);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Subscriber {
|
||||
id: u32,
|
||||
}
|
||||
|
||||
impl subscriber::Service for Subscriber {
|
||||
type ReceiveFut = Ready<()>;
|
||||
|
||||
fn receive(self, _: context::Context, message: String) -> Self::ReceiveFut {
|
||||
println!("{} received message: {}", self.id, message);
|
||||
future::ready(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Subscriber {
|
||||
async fn listen(id: u32, config: server::Config) -> io::Result<SocketAddr> {
|
||||
let incoming = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||
let addr = incoming.local_addr();
|
||||
tokio_executor::spawn(
|
||||
server::new(config)
|
||||
.incoming(incoming)
|
||||
.take(1)
|
||||
.respond_with(subscriber::serve(Subscriber { id }))
|
||||
.unit_error()
|
||||
.boxed()
|
||||
.compat(),
|
||||
);
|
||||
Ok(addr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Publisher {
|
||||
clients: Arc<Mutex<HashMap<u32, subscriber::Client>>>,
|
||||
}
|
||||
|
||||
impl Publisher {
|
||||
fn new() -> Publisher {
|
||||
Publisher {
|
||||
clients: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl publisher::Service for Publisher {
|
||||
existential type BroadcastFut: Future<Output = ()>;
|
||||
|
||||
fn broadcast(self, _: context::Context, message: String) -> Self::BroadcastFut {
|
||||
async fn broadcast(clients: Arc<Mutex<HashMap<u32, subscriber::Client>>>, message: String) {
|
||||
let mut clients = clients.lock().unwrap().clone();
|
||||
for client in clients.values_mut() {
|
||||
// Ignore failing subscribers. In a real pubsub,
|
||||
// you'd want to continually retry until subscribers
|
||||
// ack.
|
||||
let _ = await!(client.receive(context::current(), message.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
broadcast(self.clients.clone(), message)
|
||||
}
|
||||
|
||||
existential type SubscribeFut: Future<Output = Result<(), String>>;
|
||||
|
||||
fn subscribe(self, _: context::Context, id: u32, addr: SocketAddr) -> Self::SubscribeFut {
|
||||
async fn subscribe(
|
||||
clients: Arc<Mutex<HashMap<u32, subscriber::Client>>>,
|
||||
id: u32,
|
||||
addr: SocketAddr,
|
||||
) -> io::Result<()> {
|
||||
let conn = await!(bincode_transport::connect(&addr))?;
|
||||
let subscriber = await!(subscriber::new_stub(client::Config::default(), conn))?;
|
||||
println!("Subscribing {}.", id);
|
||||
clients.lock().unwrap().insert(id, subscriber);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
subscribe(Arc::clone(&self.clients), id, addr).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
existential type UnsubscribeFut: Future<Output = ()>;
|
||||
|
||||
fn unsubscribe(self, _: context::Context, id: u32) -> Self::UnsubscribeFut {
|
||||
println!("Unsubscribing {}", id);
|
||||
let mut clients = self.clients.lock().unwrap();
|
||||
if let None = clients.remove(&id) {
|
||||
eprintln!(
|
||||
"Client {} not found. Existings clients: {:?}",
|
||||
id, &*clients
|
||||
);
|
||||
}
|
||||
future::ready(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn run() -> io::Result<()> {
|
||||
env_logger::init();
|
||||
let transport = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||
let publisher_addr = transport.local_addr();
|
||||
tokio_executor::spawn(
|
||||
Server::default()
|
||||
.incoming(transport)
|
||||
.take(1)
|
||||
.respond_with(publisher::serve(Publisher::new()))
|
||||
.unit_error()
|
||||
.boxed()
|
||||
.compat(),
|
||||
);
|
||||
|
||||
let subscriber1 = await!(Subscriber::listen(0, server::Config::default()))?;
|
||||
let subscriber2 = await!(Subscriber::listen(1, server::Config::default()))?;
|
||||
|
||||
let publisher_conn = bincode_transport::connect(&publisher_addr);
|
||||
let publisher_conn = await!(publisher_conn)?;
|
||||
let mut publisher = await!(publisher::new_stub(
|
||||
client::Config::default(),
|
||||
publisher_conn
|
||||
))?;
|
||||
|
||||
if let Err(e) = await!(publisher.subscribe(context::current(), 0, subscriber1))? {
|
||||
eprintln!("Couldn't subscribe subscriber 0: {}", e);
|
||||
}
|
||||
if let Err(e) = await!(publisher.subscribe(context::current(), 1, subscriber2))? {
|
||||
eprintln!("Couldn't subscribe subscriber 1: {}", e);
|
||||
}
|
||||
|
||||
println!("Broadcasting...");
|
||||
await!(publisher.broadcast(context::current(), "hello to all".to_string()))?;
|
||||
await!(publisher.unsubscribe(context::current(), 1))?;
|
||||
await!(publisher.broadcast(context::current(), "hi again".to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tokio::run(run().boxed().map_err(|e| panic!(e)).boxed().compat());
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
94
tarpc/examples/readme.rs
Normal file
94
tarpc/examples/readme.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
#![feature(
|
||||
futures_api,
|
||||
pin,
|
||||
arbitrary_self_types,
|
||||
await_macro,
|
||||
async_await,
|
||||
proc_macro_hygiene
|
||||
)]
|
||||
|
||||
use futures::{
|
||||
compat::TokioDefaultSpawner,
|
||||
future::{self, Ready},
|
||||
prelude::*,
|
||||
};
|
||||
use rpc::{
|
||||
client, context,
|
||||
server::{Handler, Server},
|
||||
};
|
||||
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! {
|
||||
rpc hello(name: String) -> String;
|
||||
}
|
||||
|
||||
// This is the type that implements the generated Service trait. It is the business logic
|
||||
// and is used to start the server.
|
||||
#[derive(Clone)]
|
||||
struct HelloServer;
|
||||
|
||||
impl Service for HelloServer {
|
||||
// Each defined rpc generates two items in the trait, a fn that serves the RPC, and
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
|
||||
async fn run() -> io::Result<()> {
|
||||
// bincode_transport is provided by the associated crate bincode-transport. It makes it easy
|
||||
// to start up a serde-powered bincode serialization strategy over TCP.
|
||||
let transport = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||
let addr = transport.local_addr();
|
||||
|
||||
// The server is configured with the defaults.
|
||||
let server = Server::default()
|
||||
// Server can listen on any type that implements the Transport trait.
|
||||
.incoming(transport)
|
||||
// Close the stream after the client connects
|
||||
.take(1)
|
||||
// serve is generated by the tarpc::service! macro. It takes as input any type implementing
|
||||
// the generated Service trait.
|
||||
.respond_with(serve(HelloServer));
|
||||
|
||||
tokio_executor::spawn(server.unit_error().boxed().compat());
|
||||
|
||||
let transport = await!(bincode_transport::connect(&addr))?;
|
||||
|
||||
// new_stub is generated by the tarpc::service! macro. Like Server, it takes a config and any
|
||||
// Transport as input, and returns a Client, also generated by the macro.
|
||||
// by the service mcro.
|
||||
let mut client = await!(new_stub(client::Config::default(), transport))?;
|
||||
|
||||
// The client has an RPC method for each RPC defined in tarpc::service!. It takes the same args
|
||||
// as defined, with the addition of a Context, which is always the first arg. The Context
|
||||
// specifies a deadline and trace information which can be helpful in debugging requests.
|
||||
let hello = await!(client.hello(context::current(), "Stim".to_string()))?;
|
||||
|
||||
println!("{}", hello);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tarpc::init(TokioDefaultSpawner);
|
||||
|
||||
tokio::run(
|
||||
run()
|
||||
.map_err(|e| eprintln!("Oh no: {}", e))
|
||||
.boxed()
|
||||
.compat(),
|
||||
);
|
||||
}
|
||||
108
tarpc/examples/server_calling_server.rs
Normal file
108
tarpc/examples/server_calling_server.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
#![feature(
|
||||
existential_type,
|
||||
arbitrary_self_types,
|
||||
pin,
|
||||
futures_api,
|
||||
await_macro,
|
||||
async_await,
|
||||
proc_macro_hygiene
|
||||
)]
|
||||
|
||||
use crate::{add::Service as AddService, double::Service as DoubleService};
|
||||
use futures::{
|
||||
compat::TokioDefaultSpawner,
|
||||
future::{self, Ready},
|
||||
prelude::*,
|
||||
};
|
||||
use rpc::{
|
||||
client, context,
|
||||
server::{Handler, Server},
|
||||
};
|
||||
use std::io;
|
||||
|
||||
pub mod add {
|
||||
tarpc::service! {
|
||||
/// Add two ints together.
|
||||
rpc add(x: i32, y: i32) -> i32;
|
||||
}
|
||||
}
|
||||
|
||||
pub mod double {
|
||||
tarpc::service! {
|
||||
/// 2 * x
|
||||
rpc double(x: i32) -> Result<i32, String>;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AddServer;
|
||||
|
||||
impl AddService for AddServer {
|
||||
type AddFut = Ready<i32>;
|
||||
|
||||
fn add(self, _: context::Context, x: i32, y: i32) -> Self::AddFut {
|
||||
future::ready(x + y)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DoubleServer {
|
||||
add_client: add::Client,
|
||||
}
|
||||
|
||||
impl DoubleService for DoubleServer {
|
||||
existential type DoubleFut: Future<Output = Result<i32, String>> + Send;
|
||||
|
||||
fn double(self, _: context::Context, x: i32) -> Self::DoubleFut {
|
||||
async fn double(mut client: add::Client, x: i32) -> Result<i32, String> {
|
||||
let result = await!(client.add(context::current(), x, x));
|
||||
result.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
double(self.add_client.clone(), x)
|
||||
}
|
||||
}
|
||||
|
||||
async fn run() -> io::Result<()> {
|
||||
let add_listener = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||
let addr = add_listener.local_addr();
|
||||
let add_server = Server::default()
|
||||
.incoming(add_listener)
|
||||
.take(1)
|
||||
.respond_with(add::serve(AddServer));
|
||||
tokio_executor::spawn(add_server.unit_error().boxed().compat());
|
||||
|
||||
let to_add_server = await!(bincode_transport::connect(&addr))?;
|
||||
let add_client = await!(add::new_stub(client::Config::default(), to_add_server))?;
|
||||
|
||||
let double_listener = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||
let addr = double_listener.local_addr();
|
||||
let double_server = rpc::Server::default()
|
||||
.incoming(double_listener)
|
||||
.take(1)
|
||||
.respond_with(double::serve(DoubleServer { add_client }));
|
||||
tokio_executor::spawn(double_server.unit_error().boxed().compat());
|
||||
|
||||
let to_double_server = await!(bincode_transport::connect(&addr))?;
|
||||
let mut double_client = await!(double::new_stub(
|
||||
client::Config::default(),
|
||||
to_double_server
|
||||
))?;
|
||||
|
||||
for i in 1..=5 {
|
||||
println!("{:?}", await!(double_client.double(context::current(), i))?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
tarpc::init(TokioDefaultSpawner);
|
||||
tokio::run(run().map_err(|e| panic!(e)).boxed().compat());
|
||||
}
|
||||
411
tarpc/examples/service_registry.rs
Normal file
411
tarpc/examples/service_registry.rs
Normal file
@@ -0,0 +1,411 @@
|
||||
#![feature(
|
||||
pin,
|
||||
async_await,
|
||||
await_macro,
|
||||
futures_api,
|
||||
arbitrary_self_types,
|
||||
proc_macro_hygiene,
|
||||
impl_trait_in_bindings
|
||||
)]
|
||||
|
||||
mod registry {
|
||||
use bytes::Bytes;
|
||||
use futures::{
|
||||
future::{ready, Ready},
|
||||
prelude::*,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
io,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{LocalWaker, Poll},
|
||||
};
|
||||
use tarpc::{
|
||||
client::{self, Client},
|
||||
context,
|
||||
};
|
||||
|
||||
/// A request to a named service.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ServiceRequest {
|
||||
service_name: String,
|
||||
request: Bytes,
|
||||
}
|
||||
|
||||
/// A response from a named service.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ServiceResponse {
|
||||
response: Bytes,
|
||||
}
|
||||
|
||||
/// A list of registered services.
|
||||
pub struct Registry<Services> {
|
||||
registrations: Services,
|
||||
}
|
||||
|
||||
impl Default for Registry<Nil> {
|
||||
fn default() -> Self {
|
||||
Registry { registrations: Nil }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Services: MaybeServe + Sync> Registry<Services> {
|
||||
/// Returns a function that serves requests for the registered services.
|
||||
pub fn serve(
|
||||
self,
|
||||
) -> impl FnOnce(context::Context, ServiceRequest)
|
||||
-> Either<Services::Future, Ready<io::Result<ServiceResponse>>>
|
||||
+ Clone {
|
||||
let registrations = Arc::new(self.registrations);
|
||||
move |cx, req: ServiceRequest| match registrations.serve(cx, &req) {
|
||||
Some(serve) => Either::Left(serve),
|
||||
None => Either::Right(ready(Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!("Service '{}' not registered", req.service_name),
|
||||
)))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers `serve` with the given `name` using the given serialization scheme.
|
||||
pub fn register<S, Req, Resp, RespFut, Ser, De>(
|
||||
self,
|
||||
name: String,
|
||||
serve: S,
|
||||
deserialize: De,
|
||||
serialize: Ser,
|
||||
) -> Registry<Registration<impl Serve + Send + 'static, Services>>
|
||||
where
|
||||
Req: Send,
|
||||
S: FnOnce(context::Context, Req) -> RespFut + Send + 'static + Clone,
|
||||
RespFut: Future<Output = io::Result<Resp>> + Send + 'static,
|
||||
De: FnOnce(Bytes) -> io::Result<Req> + Send + 'static + Clone,
|
||||
Ser: FnOnce(Resp) -> io::Result<Bytes> + Send + 'static + Clone,
|
||||
{
|
||||
let registrations = Registration {
|
||||
name: name,
|
||||
serve: move |cx, req: Bytes| {
|
||||
async move {
|
||||
let req = deserialize.clone()(req)?;
|
||||
let response = await!(serve.clone()(cx, req))?;
|
||||
let response = serialize.clone()(response)?;
|
||||
Ok(ServiceResponse { response })
|
||||
}
|
||||
},
|
||||
rest: self.registrations,
|
||||
};
|
||||
Registry { registrations }
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a client that sends requests to a service
|
||||
/// named `service_name`, over the given channel, using
|
||||
/// the specified serialization scheme.
|
||||
pub fn new_client<Req, Resp, Ser, De>(
|
||||
service_name: String,
|
||||
channel: &client::Channel<ServiceRequest, ServiceResponse>,
|
||||
mut serialize: Ser,
|
||||
mut deserialize: De,
|
||||
) -> client::MapResponse<
|
||||
client::WithRequest<
|
||||
client::Channel<ServiceRequest, ServiceResponse>,
|
||||
impl FnMut(Req) -> ServiceRequest,
|
||||
>,
|
||||
impl FnMut(ServiceResponse) -> Resp,
|
||||
>
|
||||
where
|
||||
Req: Send + 'static,
|
||||
Resp: Send + 'static,
|
||||
De: FnMut(Bytes) -> io::Result<Resp> + Clone + Send + 'static,
|
||||
Ser: FnMut(Req) -> io::Result<Bytes> + Clone + Send + 'static,
|
||||
{
|
||||
channel
|
||||
.clone()
|
||||
.with_request(move |req| {
|
||||
ServiceRequest {
|
||||
service_name: service_name.clone(),
|
||||
// TODO: shouldn't need to unwrap here. Maybe with_request should allow for
|
||||
// returning Result.
|
||||
request: serialize(req).unwrap(),
|
||||
}
|
||||
})
|
||||
// TODO: same thing. Maybe this should be more like and_then rather than map.
|
||||
.map_response(move |resp| deserialize(resp.response).unwrap())
|
||||
}
|
||||
|
||||
/// Serves a request.
|
||||
///
|
||||
/// This trait is mostly an implementation detail that isn't used outside of the registry
|
||||
/// internals.
|
||||
pub trait Serve: Clone + Send + 'static {
|
||||
type Response: Future<Output = io::Result<ServiceResponse>> + Send + 'static;
|
||||
fn serve(self, cx: context::Context, request: Bytes) -> Self::Response;
|
||||
}
|
||||
|
||||
/// Serves a request if the request is for a registered service.
|
||||
///
|
||||
/// This trait is mostly an implementation detail that isn't used outside of the registry
|
||||
/// internals.
|
||||
pub trait MaybeServe: Send + 'static {
|
||||
type Future: Future<Output = io::Result<ServiceResponse>> + Send + 'static;
|
||||
|
||||
fn serve(&self, cx: context::Context, request: &ServiceRequest) -> Option<Self::Future>;
|
||||
}
|
||||
|
||||
/// A registry starting with service S, followed by Rest.
|
||||
///
|
||||
/// This type is mostly an implementation detail that is not used directly
|
||||
/// outside of the registry internals.
|
||||
pub struct Registration<S, Rest> {
|
||||
/// The registered service's name. Must be unique across all registered services.
|
||||
name: String,
|
||||
/// The registered service.
|
||||
serve: S,
|
||||
/// Any remaining registered services.
|
||||
rest: Rest,
|
||||
}
|
||||
|
||||
/// An empty registry.
|
||||
///
|
||||
/// This type is mostly an implementation detail that is not used directly
|
||||
/// outside of the registry internals.
|
||||
pub struct Nil;
|
||||
|
||||
impl MaybeServe for Nil {
|
||||
type Future = futures::future::Ready<io::Result<ServiceResponse>>;
|
||||
|
||||
fn serve(&self, _: context::Context, _: &ServiceRequest) -> Option<Self::Future> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Rest> MaybeServe for Registration<S, Rest>
|
||||
where
|
||||
S: Serve,
|
||||
Rest: MaybeServe,
|
||||
{
|
||||
type Future = Either<S::Response, Rest::Future>;
|
||||
|
||||
fn serve(&self, cx: context::Context, request: &ServiceRequest) -> Option<Self::Future> {
|
||||
if self.name == request.service_name {
|
||||
Some(Either::Left(
|
||||
self.serve.clone().serve(cx, request.request.clone()),
|
||||
))
|
||||
} else {
|
||||
self.rest.serve(cx, request).map(Either::Right)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps either of two future types that both resolve to the same output type.
|
||||
#[derive(Debug)]
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub enum Either<Left, Right> {
|
||||
Left(Left),
|
||||
Right(Right),
|
||||
}
|
||||
|
||||
impl<Output, Left, Right> Future for Either<Left, Right>
|
||||
where
|
||||
Left: Future<Output = Output>,
|
||||
Right: Future<Output = Output>,
|
||||
{
|
||||
type Output = Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Output> {
|
||||
unsafe {
|
||||
match Pin::get_mut_unchecked(self) {
|
||||
Either::Left(car) => Pin::new_unchecked(car).poll(waker),
|
||||
Either::Right(cdr) => Pin::new_unchecked(cdr).poll(waker),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Resp, F> Serve for F
|
||||
where
|
||||
F: FnOnce(context::Context, Bytes) -> Resp + Clone + Send + 'static,
|
||||
Resp: Future<Output = io::Result<ServiceResponse>> + Send + 'static,
|
||||
{
|
||||
type Response = Resp;
|
||||
|
||||
fn serve(self, cx: context::Context, request: Bytes) -> Resp {
|
||||
self(cx, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example
|
||||
use bytes::Bytes;
|
||||
use futures::{
|
||||
future::{ready, Ready},
|
||||
prelude::*,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use tarpc::{client, context, server::Handler};
|
||||
|
||||
fn deserialize<Req>(req: Bytes) -> io::Result<Req>
|
||||
where
|
||||
Req: for<'a> Deserialize<'a> + Send,
|
||||
{
|
||||
bincode::deserialize(req.as_ref()).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
}
|
||||
|
||||
fn serialize<Resp>(resp: Resp) -> io::Result<Bytes>
|
||||
where
|
||||
Resp: Serialize,
|
||||
{
|
||||
Ok(bincode::serialize(&resp)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?
|
||||
.into())
|
||||
}
|
||||
|
||||
mod write_service {
|
||||
tarpc::service! {
|
||||
rpc write(key: String, value: String);
|
||||
}
|
||||
}
|
||||
|
||||
mod read_service {
|
||||
tarpc::service! {
|
||||
rpc read(key: String) -> Option<String>;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct Server {
|
||||
data: Arc<RwLock<HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
impl write_service::Service for Server {
|
||||
type WriteFut = Ready<()>;
|
||||
|
||||
fn write(self, _: context::Context, key: String, value: String) -> Self::WriteFut {
|
||||
self.data.write().unwrap().insert(key, value);
|
||||
ready(())
|
||||
}
|
||||
}
|
||||
|
||||
impl read_service::Service for Server {
|
||||
type ReadFut = Ready<Option<String>>;
|
||||
|
||||
fn read(self, _: context::Context, key: String) -> Self::ReadFut {
|
||||
ready(self.data.read().unwrap().get(&key).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
trait DefaultSpawn {
|
||||
fn spawn(self);
|
||||
}
|
||||
|
||||
impl<F> DefaultSpawn for F
|
||||
where
|
||||
F: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
fn spawn(self) {
|
||||
tokio_executor::spawn(self.unit_error().boxed().compat())
|
||||
}
|
||||
}
|
||||
|
||||
struct BincodeRegistry<Services> {
|
||||
registry: registry::Registry<Services>,
|
||||
}
|
||||
|
||||
impl Default for BincodeRegistry<registry::Nil> {
|
||||
fn default() -> Self {
|
||||
BincodeRegistry {
|
||||
registry: registry::Registry::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Services: registry::MaybeServe + Sync> BincodeRegistry<Services> {
|
||||
fn serve(
|
||||
self,
|
||||
) -> impl FnOnce(
|
||||
context::Context, registry::ServiceRequest
|
||||
) -> registry::Either<
|
||||
Services::Future,
|
||||
Ready<io::Result<registry::ServiceResponse>>,
|
||||
> + Clone {
|
||||
self.registry.serve()
|
||||
}
|
||||
|
||||
fn register<S, Req, Resp, RespFut>(
|
||||
self,
|
||||
name: String,
|
||||
serve: S,
|
||||
) -> BincodeRegistry<registry::Registration<impl registry::Serve + Send + 'static, Services>>
|
||||
where
|
||||
Req: for<'a> Deserialize<'a> + Send + 'static,
|
||||
Resp: Serialize + 'static,
|
||||
S: FnOnce(context::Context, Req) -> RespFut + Send + 'static + Clone,
|
||||
RespFut: Future<Output = io::Result<Resp>> + Send + 'static,
|
||||
{
|
||||
let registry = self.registry.register(name, serve, deserialize, serialize);
|
||||
BincodeRegistry { registry }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_client<Req, Resp>(
|
||||
service_name: String,
|
||||
channel: &client::Channel<registry::ServiceRequest, registry::ServiceResponse>,
|
||||
) -> client::MapResponse<
|
||||
client::WithRequest<
|
||||
client::Channel<registry::ServiceRequest, registry::ServiceResponse>,
|
||||
impl FnMut(Req) -> registry::ServiceRequest,
|
||||
>,
|
||||
impl FnMut(registry::ServiceResponse) -> Resp,
|
||||
>
|
||||
where
|
||||
Req: Serialize + Send + 'static,
|
||||
Resp: for<'a> Deserialize<'a> + Send + 'static,
|
||||
{
|
||||
registry::new_client(service_name, channel, serialize, deserialize)
|
||||
}
|
||||
|
||||
async fn run() -> io::Result<()> {
|
||||
let server = Server::default();
|
||||
let registry = BincodeRegistry::default()
|
||||
.register(
|
||||
"WriteService".to_string(),
|
||||
write_service::serve(server.clone()),
|
||||
)
|
||||
.register(
|
||||
"ReadService".to_string(),
|
||||
read_service::serve(server.clone()),
|
||||
);
|
||||
|
||||
let listener = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||
let server_addr = listener.local_addr();
|
||||
let server = tarpc::Server::default()
|
||||
.incoming(listener)
|
||||
.take(1)
|
||||
.respond_with(registry.serve());
|
||||
tokio_executor::spawn(server.unit_error().boxed().compat());
|
||||
|
||||
let transport = await!(bincode_transport::connect(&server_addr))?;
|
||||
let channel = await!(client::new(client::Config::default(), transport))?;
|
||||
|
||||
let write_client = new_client("WriteService".to_string(), &channel);
|
||||
let mut write_client = write_service::Client::from(write_client);
|
||||
|
||||
let read_client = new_client("ReadService".to_string(), &channel);
|
||||
let mut read_client = read_service::Client::from(read_client);
|
||||
|
||||
await!(write_client.write(context::current(), "key".to_string(), "val".to_string()))?;
|
||||
let val = await!(read_client.read(context::current(), "key".to_string()))?;
|
||||
println!("{:?}", val);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tarpc::init(futures::compat::TokioDefaultSpawner);
|
||||
tokio::run(run().boxed().map_err(|e| panic!(e)).boxed().compat());
|
||||
}
|
||||
1
tarpc/rustfmt.toml
Normal file
1
tarpc/rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
edition = "2018"
|
||||
169
tarpc/src/lib.rs
Normal file
169
tarpc/src/lib.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! ## tarpc: Tim & Adam's RPC lib
|
||||
//! [](https://travis-ci.org/google/tarpc)
|
||||
//! [](https://coveralls.io/github/google/tarpc?branch=master)
|
||||
//! [](LICENSE)
|
||||
//! [](https://crates.io/crates/tarpc)
|
||||
//! [](https://gitter.im/tarpc/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
//!
|
||||
//! *Disclaimer*: This is not an official Google product.
|
||||
//!
|
||||
//! tarpc is an RPC framework for rust with a focus on ease of use. Defining a
|
||||
//! service can be done in just a few lines of code, and most of the boilerplate of
|
||||
//! writing a server is taken care of for you.
|
||||
//!
|
||||
//! [Documentation](https://docs.rs/crate/tarpc/)
|
||||
//!
|
||||
//! ## What is an RPC framework?
|
||||
//! "RPC" stands for "Remote Procedure Call," a function call where the work of
|
||||
//! producing the return value is being done somewhere else. When an rpc function is
|
||||
//! invoked, behind the scenes the function contacts some other process somewhere
|
||||
//! and asks them to evaluate the function instead. The original function then
|
||||
//! returns the value produced by the other process.
|
||||
//!
|
||||
//! RPC frameworks are a fundamental building block of most microservices-oriented
|
||||
//! architectures. Two well-known ones are [gRPC](http://www.grpc.io) and
|
||||
//! [Cap'n Proto](https://capnproto.org/).
|
||||
//!
|
||||
//! tarpc differentiates itself from other RPC frameworks by defining the schema in code,
|
||||
//! rather than in a separate language such as .proto. This means there's no separate compilation
|
||||
//! process, and no cognitive context switching between different languages. Additionally, it
|
||||
//! works with the community-backed library serde: any serde-serializable type can be used as
|
||||
//! arguments to tarpc fns.
|
||||
//!
|
||||
//! ## Usage
|
||||
//! Add to your `Cargo.toml` dependencies:
|
||||
//!
|
||||
//! ```toml
|
||||
//! tarpc = "0.14.0"
|
||||
//! ```
|
||||
//!
|
||||
//! The `service!` macro expands to a collection of items that form an
|
||||
//! rpc service. In the above example, the macro is called within the
|
||||
//! `hello_service` module. This module will contain a `Client` stub and `Service` trait. There is
|
||||
//! These generated types make it easy and ergonomic to write servers without dealing with serialization
|
||||
//! directly. Simply implement one of the generated traits, and you're off to the
|
||||
//! races!
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! Here's a small service.
|
||||
//!
|
||||
//! ```rust
|
||||
//! #![feature(futures_api, pin, arbitrary_self_types, await_macro, async_await, proc_macro_hygiene)]
|
||||
//!
|
||||
//!
|
||||
//! use futures::{
|
||||
//! compat::TokioDefaultSpawner,
|
||||
//! future::{self, Ready},
|
||||
//! prelude::*,
|
||||
//! };
|
||||
//! use tarpc::{
|
||||
//! client, context,
|
||||
//! server::{self, Handler},
|
||||
//! };
|
||||
//! 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! {
|
||||
//! /// Returns a greeting for name.
|
||||
//! rpc hello(name: String) -> String;
|
||||
//! }
|
||||
//!
|
||||
//! // This is the type that implements the generated Service trait. It is the business logic
|
||||
//! // and is used to start the server.
|
||||
//! #[derive(Clone)]
|
||||
//! struct HelloServer;
|
||||
//!
|
||||
//! impl Service for HelloServer {
|
||||
//! // Each defined rpc generates two items in the trait, a fn that serves the RPC, and
|
||||
//! // 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))
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! async fn run() -> io::Result<()> {
|
||||
//! // bincode_transport is provided by the associated crate bincode-transport. It makes it easy
|
||||
//! // to start up a serde-powered bincode serialization strategy over TCP.
|
||||
//! let transport = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||
//! let addr = transport.local_addr();
|
||||
//!
|
||||
//! // The server is configured with the defaults.
|
||||
//! let server = server::new(server::Config::default())
|
||||
//! // Server can listen on any type that implements the Transport trait.
|
||||
//! .incoming(transport)
|
||||
//! // Close the stream after the client connects
|
||||
//! .take(1)
|
||||
//! // serve is generated by the service! macro. It takes as input any type implementing
|
||||
//! // the generated Service trait.
|
||||
//! .respond_with(serve(HelloServer));
|
||||
//!
|
||||
//! tokio_executor::spawn(server.unit_error().boxed().compat());
|
||||
//!
|
||||
//! let transport = await!(bincode_transport::connect(&addr))?;
|
||||
//!
|
||||
//! // new_stub is generated by the service! macro. Like Server, it takes a config and any
|
||||
//! // Transport as input, and returns a Client, also generated by the macro.
|
||||
//! // by the service mcro.
|
||||
//! let mut client = await!(new_stub(client::Config::default(), transport))?;
|
||||
//!
|
||||
//! // The client has an RPC method for each RPC defined in service!. It takes the same args
|
||||
//! // as defined, with the addition of a Context, which is always the first arg. The Context
|
||||
//! // specifies a deadline and trace information which can be helpful in debugging requests.
|
||||
//! let hello = await!(client.hello(context::current(), "Stim".to_string()))?;
|
||||
//!
|
||||
//! println!("{}", hello);
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! tarpc::init(TokioDefaultSpawner);
|
||||
//! tokio::run(run()
|
||||
//! .map_err(|e| eprintln!("Oh no: {}", e))
|
||||
//! .boxed()
|
||||
//! .compat(),
|
||||
//! );
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Service Documentation
|
||||
//!
|
||||
//! Use `cargo doc` as you normally would to see the documentation created for all
|
||||
//! items expanded by a `service!` invocation.
|
||||
|
||||
#![deny(missing_docs, missing_debug_implementations)]
|
||||
#![feature(async_await)]
|
||||
#![cfg_attr(
|
||||
test,
|
||||
feature(
|
||||
pin,
|
||||
futures_api,
|
||||
await_macro,
|
||||
proc_macro_hygiene,
|
||||
arbitrary_self_types
|
||||
)
|
||||
)]
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use futures;
|
||||
pub use rpc::*;
|
||||
#[cfg(feature = "serde")]
|
||||
#[doc(hidden)]
|
||||
pub use serde;
|
||||
#[doc(hidden)]
|
||||
pub use tarpc_plugins::*;
|
||||
|
||||
/// Provides the macro used for constructing rpc services and client stubs.
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
377
tarpc/src/macros.rs
Normal file
377
tarpc/src/macros.rs
Normal file
@@ -0,0 +1,377 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! add_serde_if_enabled {
|
||||
($(#[$attr:meta])* -- $i:item) => {
|
||||
$(#[$attr])*
|
||||
#[derive($crate::serde::Serialize, $crate::serde::Deserialize)]
|
||||
$i
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "serde"))]
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! add_serde_if_enabled {
|
||||
($(#[$attr:meta])* -- $i:item) => {
|
||||
$(#[$attr])*
|
||||
$i
|
||||
}
|
||||
}
|
||||
|
||||
/// The main macro that creates RPC services.
|
||||
///
|
||||
/// Rpc methods are specified, mirroring trait syntax:
|
||||
///
|
||||
/// ```
|
||||
/// # #![feature(await_macro, pin, arbitrary_self_types, async_await, futures_api, proc_macro_hygiene)]
|
||||
/// # fn main() {}
|
||||
/// # tarpc::service! {
|
||||
/// /// Say hello
|
||||
/// rpc hello(name: String) -> String;
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Attributes can be attached to each rpc. These attributes
|
||||
/// will then be attached to the generated service traits'
|
||||
/// corresponding `fn`s, as well as to the client stubs' RPCs.
|
||||
///
|
||||
/// The following items are expanded in the enclosing module:
|
||||
///
|
||||
/// * `trait Service` -- defines the RPC service.
|
||||
/// * `fn serve` -- turns a service impl into a request handler.
|
||||
/// * `Client` -- a client stub with a fn for each RPC.
|
||||
/// * `fn new_stub` -- creates a new Client stub.
|
||||
///
|
||||
#[macro_export]
|
||||
macro_rules! service {
|
||||
() => {
|
||||
compile_error!("Must define at least one RPC method.");
|
||||
};
|
||||
// Entry point
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
rpc $fn_name:ident( $( $arg:ident : $in_:ty ),* ) $(-> $out:ty)*;
|
||||
)*
|
||||
) => {
|
||||
$crate::service! {{
|
||||
$(
|
||||
$(#[$attr])*
|
||||
rpc $fn_name( $( $arg : $in_ ),* ) $(-> $out)*;
|
||||
)*
|
||||
}}
|
||||
};
|
||||
// Pattern for when the next rpc has an implicit unit return type.
|
||||
(
|
||||
{
|
||||
$(#[$attr:meta])*
|
||||
rpc $fn_name:ident( $( $arg:ident : $in_:ty ),* );
|
||||
|
||||
$( $unexpanded:tt )*
|
||||
}
|
||||
$( $expanded:tt )*
|
||||
) => {
|
||||
$crate::service! {
|
||||
{ $( $unexpanded )* }
|
||||
|
||||
$( $expanded )*
|
||||
|
||||
$(#[$attr])*
|
||||
rpc $fn_name( $( $arg : $in_ ),* ) -> ();
|
||||
}
|
||||
};
|
||||
// Pattern for when the next rpc has an explicit return type.
|
||||
(
|
||||
{
|
||||
$(#[$attr:meta])*
|
||||
rpc $fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty;
|
||||
|
||||
$( $unexpanded:tt )*
|
||||
}
|
||||
$( $expanded:tt )*
|
||||
) => {
|
||||
$crate::service! {
|
||||
{ $( $unexpanded )* }
|
||||
|
||||
$( $expanded )*
|
||||
|
||||
$(#[$attr])*
|
||||
rpc $fn_name( $( $arg : $in_ ),* ) -> $out;
|
||||
}
|
||||
};
|
||||
// Pattern for when all return types have been expanded
|
||||
(
|
||||
{ } // none left to expand
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
rpc $fn_name:ident ( $( $arg:ident : $in_:ty ),* ) -> $out:ty;
|
||||
)*
|
||||
) => {
|
||||
$crate::add_serde_if_enabled! {
|
||||
/// The request sent over the wire from the client to the server.
|
||||
#[derive(Debug)]
|
||||
#[allow(non_camel_case_types, unused)]
|
||||
--
|
||||
pub enum Request {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
$fn_name{ $($arg: $in_,)* }
|
||||
),*
|
||||
}
|
||||
}
|
||||
|
||||
$crate::add_serde_if_enabled! {
|
||||
/// The response sent over the wire from the server to the client.
|
||||
#[derive(Debug)]
|
||||
#[allow(non_camel_case_types, unused)]
|
||||
--
|
||||
pub enum Response {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
$fn_name($out)
|
||||
),*
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: proc_macro can't currently parse $crate, so this needs to be imported for the
|
||||
// usage of snake_to_camel! to work.
|
||||
use $crate::futures::Future as Future__;
|
||||
|
||||
/// Defines the RPC service. The additional trait bounds are required so that services can
|
||||
/// multiplex requests across multiple tasks, potentially on multiple threads.
|
||||
pub trait Service: Clone + Send + 'static {
|
||||
$(
|
||||
$crate::snake_to_camel! {
|
||||
/// The type of future returned by `{}`.
|
||||
type $fn_name: Future__<Output = $out> + Send;
|
||||
}
|
||||
|
||||
$(#[$attr])*
|
||||
fn $fn_name(self, ctx: $crate::context::Context, $($arg:$in_),*) -> $crate::ty_snake_to_camel!(Self::$fn_name);
|
||||
)*
|
||||
}
|
||||
|
||||
// TODO: use an existential type instead of this when existential types work.
|
||||
/// A future resolving to a server [`Response`].
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum ResponseFut<S: Service> {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
$fn_name($crate::ty_snake_to_camel!(<S as Service>::$fn_name)),
|
||||
)*
|
||||
}
|
||||
|
||||
impl<S: Service> ::std::fmt::Debug for ResponseFut<S> {
|
||||
fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
fmt.debug_struct("Response").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Service> ::std::future::Future for ResponseFut<S> {
|
||||
type Output = ::std::io::Result<Response>;
|
||||
|
||||
fn poll(self: ::std::pin::Pin<&mut Self>, waker: &::std::task::LocalWaker)
|
||||
-> ::std::task::Poll<::std::io::Result<Response>>
|
||||
{
|
||||
unsafe {
|
||||
match ::std::pin::Pin::get_mut_unchecked(self) {
|
||||
$(
|
||||
ResponseFut::$fn_name(resp) =>
|
||||
::std::pin::Pin::new_unchecked(resp)
|
||||
.poll(waker)
|
||||
.map(Response::$fn_name)
|
||||
.map(Ok),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a serving function to use with rpc::server::Server.
|
||||
pub fn serve<S: Service>(service: S)
|
||||
-> impl FnOnce($crate::context::Context, Request) -> ResponseFut<S> + Send + 'static + Clone {
|
||||
move |ctx, req| {
|
||||
match req {
|
||||
$(
|
||||
Request::$fn_name{ $($arg,)* } => {
|
||||
let resp = Service::$fn_name(service.clone(), ctx, $($arg),*);
|
||||
ResponseFut::$fn_name(resp)
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Clone, Debug)]
|
||||
/// The client stub that makes RPC calls to the server. Exposes a Future interface.
|
||||
pub struct Client<C = $crate::client::Channel<Request, Response>>(C);
|
||||
|
||||
/// Returns a new client stub that sends requests over the given transport.
|
||||
pub async fn new_stub<T>(config: $crate::client::Config, transport: T)
|
||||
-> ::std::io::Result<Client>
|
||||
where
|
||||
T: $crate::Transport<
|
||||
Item = $crate::Response<Response>,
|
||||
SinkItem = $crate::ClientMessage<Request>> + Send + 'static,
|
||||
{
|
||||
Ok(Client(await!($crate::client::new(config, transport))?))
|
||||
}
|
||||
|
||||
impl<C> From<C> for Client<C>
|
||||
where for <'a> C: $crate::Client<'a, Request, Response = Response>
|
||||
{
|
||||
fn from(client: C) -> Self {
|
||||
Client(client)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Client<C>
|
||||
where for<'a> C: $crate::Client<'a, Request, Response = Response>
|
||||
{
|
||||
$(
|
||||
#[allow(unused)]
|
||||
$(#[$attr])*
|
||||
pub fn $fn_name(&mut self, ctx: $crate::context::Context, $($arg: $in_),*)
|
||||
-> impl ::std::future::Future<Output = ::std::io::Result<$out>> + '_ {
|
||||
let request__ = Request::$fn_name { $($arg,)* };
|
||||
let resp = $crate::Client::call(&mut self.0, ctx, request__);
|
||||
async move {
|
||||
match await!(resp)? {
|
||||
Response::$fn_name(msg__) => ::std::result::Result::Ok(msg__),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// allow dead code; we're just testing that the macro expansion compiles
|
||||
#[allow(dead_code)]
|
||||
#[cfg(test)]
|
||||
mod syntax_test {
|
||||
service! {
|
||||
#[deny(warnings)]
|
||||
#[allow(non_snake_case)]
|
||||
rpc TestCamelCaseDoesntConflict();
|
||||
rpc hello() -> String;
|
||||
#[doc="attr"]
|
||||
rpc attr(s: String) -> String;
|
||||
rpc no_args_no_return();
|
||||
rpc no_args() -> ();
|
||||
rpc one_arg(foo: String) -> i32;
|
||||
rpc two_args_no_return(bar: String, baz: u64);
|
||||
rpc two_args(bar: String, baz: u64) -> String;
|
||||
rpc no_args_ret_error() -> i32;
|
||||
rpc one_arg_ret_error(foo: String) -> String;
|
||||
rpc no_arg_implicit_return_error();
|
||||
#[doc="attr"]
|
||||
rpc one_arg_implicit_return_error(foo: String);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod functional_test {
|
||||
use futures::{
|
||||
compat::TokioDefaultSpawner,
|
||||
future::{ready, Ready},
|
||||
prelude::*,
|
||||
};
|
||||
use rpc::{client, context, server::Handler, transport::channel};
|
||||
use std::io;
|
||||
use tokio::runtime::current_thread;
|
||||
|
||||
service! {
|
||||
rpc add(x: i32, y: i32) -> i32;
|
||||
rpc hey(name: String) -> String;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Server;
|
||||
|
||||
impl Service for Server {
|
||||
type AddFut = Ready<i32>;
|
||||
|
||||
fn add(self, _: context::Context, x: i32, y: i32) -> Self::AddFut {
|
||||
ready(x + y)
|
||||
}
|
||||
|
||||
type HeyFut = Ready<String>;
|
||||
|
||||
fn hey(self, _: context::Context, name: String) -> Self::HeyFut {
|
||||
ready(format!("Hey, {}.", name))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sequential() {
|
||||
let _ = env_logger::try_init();
|
||||
rpc::init(TokioDefaultSpawner);
|
||||
|
||||
let test = async {
|
||||
let (tx, rx) = channel::unbounded();
|
||||
tokio_executor::spawn(
|
||||
crate::Server::default()
|
||||
.incoming(stream::once(ready(Ok(rx))))
|
||||
.respond_with(serve(Server))
|
||||
.unit_error()
|
||||
.boxed()
|
||||
.compat(),
|
||||
);
|
||||
|
||||
let mut client = await!(new_stub(client::Config::default(), tx))?;
|
||||
assert_eq!(3, await!(client.add(context::current(), 1, 2))?);
|
||||
assert_eq!(
|
||||
"Hey, Tim.",
|
||||
await!(client.hey(context::current(), "Tim".to_string()))?
|
||||
);
|
||||
Ok::<_, io::Error>(())
|
||||
}
|
||||
.map_err(|e| panic!(e.to_string()));
|
||||
|
||||
current_thread::block_on_all(test.boxed().compat()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concurrent() {
|
||||
let _ = env_logger::try_init();
|
||||
rpc::init(TokioDefaultSpawner);
|
||||
|
||||
let test = async {
|
||||
let (tx, rx) = channel::unbounded();
|
||||
tokio_executor::spawn(
|
||||
rpc::Server::default()
|
||||
.incoming(stream::once(ready(Ok(rx))))
|
||||
.respond_with(serve(Server))
|
||||
.unit_error()
|
||||
.boxed()
|
||||
.compat(),
|
||||
);
|
||||
|
||||
let client = await!(new_stub(client::Config::default(), tx))?;
|
||||
let mut c = client.clone();
|
||||
let req1 = c.add(context::current(), 1, 2);
|
||||
let mut c = client.clone();
|
||||
let req2 = c.add(context::current(), 3, 4);
|
||||
let mut c = client.clone();
|
||||
let req3 = c.hey(context::current(), "Tim".to_string());
|
||||
|
||||
assert_eq!(3, await!(req1)?);
|
||||
assert_eq!(7, await!(req2)?);
|
||||
assert_eq!("Hey, Tim.", await!(req3)?);
|
||||
Ok::<_, io::Error>(())
|
||||
}
|
||||
.map_err(|e| panic!("test failed: {}", e));
|
||||
|
||||
current_thread::block_on_all(test.boxed().compat()).unwrap();
|
||||
}
|
||||
}
|
||||
126
tarpc/tests/latency.rs
Normal file
126
tarpc/tests/latency.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
#![feature(
|
||||
test,
|
||||
arbitrary_self_types,
|
||||
pin,
|
||||
integer_atomics,
|
||||
futures_api,
|
||||
generators,
|
||||
await_macro,
|
||||
async_await,
|
||||
proc_macro_hygiene
|
||||
)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use self::test::stats::Stats;
|
||||
use futures::{compat::TokioDefaultSpawner, future, prelude::*};
|
||||
use rpc::{
|
||||
client, context,
|
||||
server::{Handler, Server},
|
||||
};
|
||||
use std::{
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
mod ack {
|
||||
tarpc::service! {
|
||||
rpc ack();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Serve;
|
||||
|
||||
impl ack::Service for Serve {
|
||||
type AckFut = future::Ready<()>;
|
||||
|
||||
fn ack(self, _: context::Context) -> Self::AckFut {
|
||||
future::ready(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn bench() -> io::Result<()> {
|
||||
let listener = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||
let addr = listener.local_addr();
|
||||
|
||||
tokio_executor::spawn(
|
||||
Server::default()
|
||||
.incoming(listener)
|
||||
.take(1)
|
||||
.respond_with(ack::serve(Serve))
|
||||
.unit_error()
|
||||
.boxed()
|
||||
.compat(),
|
||||
);
|
||||
|
||||
let conn = await!(bincode_transport::connect(&addr))?;
|
||||
let mut client = await!(ack::new_stub(client::Config::default(), conn))?;
|
||||
|
||||
let total = 10_000usize;
|
||||
let mut successful = 0u32;
|
||||
let mut unsuccessful = 0u32;
|
||||
let mut durations = vec![];
|
||||
for _ in 1..=total {
|
||||
let now = Instant::now();
|
||||
let response = await!(client.ack(context::current()));
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
match response {
|
||||
Ok(_) => successful += 1,
|
||||
Err(_) => unsuccessful += 1,
|
||||
};
|
||||
durations.push(elapsed);
|
||||
}
|
||||
|
||||
let durations_nanos = durations
|
||||
.iter()
|
||||
.map(|duration| duration.as_secs() as f64 * 1E9 + duration.subsec_nanos() as f64)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (lower, median, upper) = durations_nanos.quartiles();
|
||||
|
||||
println!("Of {:?} runs:", durations_nanos.len());
|
||||
println!("\tSuccessful: {:?}", successful);
|
||||
println!("\tUnsuccessful: {:?}", unsuccessful);
|
||||
println!(
|
||||
"\tMean: {:?}",
|
||||
Duration::from_nanos(durations_nanos.mean() as u64)
|
||||
);
|
||||
println!("\tMedian: {:?}", Duration::from_nanos(median as u64));
|
||||
println!(
|
||||
"\tStd Dev: {:?}",
|
||||
Duration::from_nanos(durations_nanos.std_dev() as u64)
|
||||
);
|
||||
println!(
|
||||
"\tMin: {:?}",
|
||||
Duration::from_nanos(durations_nanos.min() as u64)
|
||||
);
|
||||
println!(
|
||||
"\tMax: {:?}",
|
||||
Duration::from_nanos(durations_nanos.max() as u64)
|
||||
);
|
||||
println!(
|
||||
"\tQuartiles: ({:?}, {:?}, {:?})",
|
||||
Duration::from_nanos(lower as u64),
|
||||
Duration::from_nanos(median as u64),
|
||||
Duration::from_nanos(upper as u64)
|
||||
);
|
||||
|
||||
println!("done");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bench_small_packet() {
|
||||
env_logger::init();
|
||||
tarpc::init(TokioDefaultSpawner);
|
||||
|
||||
tokio::run(bench().map_err(|e| panic!(e.to_string())).boxed().compat())
|
||||
}
|
||||
Binary file not shown.
BIN
test/root-ca.der
BIN
test/root-ca.der
Binary file not shown.
@@ -1,21 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAOIvDiVb18eVMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTYwODE0MTY1NjExWhcNMjYwODEyMTY1NjExWjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEArVHWFn52Lbl1l59exduZntVSZyDYpzDND+S2LUcO6fRBWhV/1Kzox+2G
|
||||
ZptbuMGmfI3iAnb0CFT4uC3kBkQQlXonGATSVyaFTFR+jq/lc0SP+9Bd7SBXieIV
|
||||
eIXlY1TvlwIvj3Ntw9zX+scTA4SXxH6M0rKv9gTOub2vCMSHeF16X8DQr4XsZuQr
|
||||
7Cp7j1I4aqOJyap5JTl5ijmG8cnu0n+8UcRlBzy99dLWJG0AfI3VRJdWpGTNVZ92
|
||||
aFff3RpK3F/WI2gp3qV1ynRAKuvmncGC3LDvYfcc2dgsc1N6Ffq8GIrkgRob6eBc
|
||||
klDHp1d023Lwre+VaVDSo1//Y72UFwIDAQABo1AwTjAdBgNVHQ4EFgQUbNOlA6sN
|
||||
XyzJjYqciKeId7g3/ZowHwYDVR0jBBgwFoAUbNOlA6sNXyzJjYqciKeId7g3/Zow
|
||||
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVVaR5QWLZIRR4Dw6TSBn
|
||||
BQiLpBSXN6oAxdDw6n4PtwW6CzydaA+creiK6LfwEsiifUfQe9f+T+TBSpdIYtMv
|
||||
Z2H2tjlFX8VrjUFvPrvn5c28CuLI0foBgY8XGSkR2YMYzWw2jPEq3Th/KM5Catn3
|
||||
AFm3bGKWMtGPR4v+90chEN0jzaAmJYRrVUh9vea27bOCn31Nse6XXQPmSI6Gyncy
|
||||
OAPUsvPClF3IjeL1tmBotWqSGn1cYxLo+Lwjk22A9h6vjcNQRyZF2VLVvtwYrNU3
|
||||
mwJ6GCLsLHpwW/yjyvn8iEltnJvByM/eeRnfXV6WDObyiZsE/n6DxIRJodQzFqy9
|
||||
GA==
|
||||
-----END CERTIFICATE-----
|
||||
21
trace/Cargo.toml
Normal file
21
trace/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "tarpc-trace"
|
||||
version = "0.1.0"
|
||||
authors = ["tikue <tikue@google.com>"]
|
||||
edition = '2018'
|
||||
license = "MIT"
|
||||
documentation = "https://docs.rs/tarpc-trace"
|
||||
homepage = "https://github.com/google/tarpc"
|
||||
repository = "https://github.com/google/tarpc"
|
||||
keywords = ["rpc", "network", "server", "api", "tls"]
|
||||
categories = ["asynchronous", "network-programming"]
|
||||
readme = "../README.md"
|
||||
description = "foundations for tracing in tarpc"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.5"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
optional = true
|
||||
features = ["derive"]
|
||||
1
trace/rustfmt.toml
Normal file
1
trace/rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
edition = "2018"
|
||||
106
trace/src/lib.rs
Normal file
106
trace/src/lib.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
#![deny(missing_docs, missing_debug_implementations)]
|
||||
|
||||
//! Provides building blocks for tracing distributed programs.
|
||||
//!
|
||||
//! A trace is logically a tree of causally-related events called spans. Traces are tracked via a
|
||||
//! [context](Context) that identifies the current trace, span, and parent of the current span. In
|
||||
//! distributed systems, a context can be sent from client to server to connect events occurring on
|
||||
//! either side.
|
||||
//!
|
||||
//! This crate's design is based on [opencensus
|
||||
//! tracing](https://opencensus.io/core-concepts/tracing/).
|
||||
|
||||
use rand::Rng;
|
||||
use std::{
|
||||
fmt::{self, Formatter},
|
||||
mem,
|
||||
};
|
||||
|
||||
/// A context for tracing the execution of processes, distributed or otherwise.
|
||||
///
|
||||
/// Consists of a span identifying an event, an optional parent span identifying a causal event
|
||||
/// that triggered the current span, and a trace with which all related spans are associated.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Serialize, serde::Deserialize)
|
||||
)]
|
||||
pub struct Context {
|
||||
/// An identifier of the trace associated with the current context. A trace ID is typically
|
||||
/// created at a root span and passed along through all causal events.
|
||||
pub trace_id: TraceId,
|
||||
/// An identifier of the current span. In typical RPC usage, a span is created by a client
|
||||
/// before making an RPC, and the span ID is sent to the server. The server is free to create
|
||||
/// its own spans, for which it sets the client's span as the parent span.
|
||||
pub span_id: SpanId,
|
||||
/// An identifier of the span that originated the current span. For example, if a server sends
|
||||
/// an RPC in response to a client request that included a span, the server would create a span
|
||||
/// for the RPC and set its parent to the span_id in the incoming request's context.
|
||||
///
|
||||
/// If `parent_id` is `None`, then this is a root context.
|
||||
pub parent_id: Option<SpanId>,
|
||||
}
|
||||
|
||||
/// A 128-bit UUID identifying a trace. All spans caused by the same originating span share the
|
||||
/// same trace ID.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Serialize, serde::Deserialize)
|
||||
)]
|
||||
pub struct TraceId(u128);
|
||||
|
||||
/// A 64-bit identifier of a span within a trace. The identifier is unique within the span's trace.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Serialize, serde::Deserialize)
|
||||
)]
|
||||
pub struct SpanId(u64);
|
||||
|
||||
impl Context {
|
||||
/// Constructs a new root context. A root context is one with no parent span.
|
||||
pub fn new_root() -> Self {
|
||||
let rng = &mut rand::thread_rng();
|
||||
Context {
|
||||
trace_id: TraceId::random(rng),
|
||||
span_id: SpanId::random(rng),
|
||||
parent_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TraceId {
|
||||
/// Returns a random trace ID that can be assumed to be globally unique if `rng` generates
|
||||
/// actually-random numbers.
|
||||
pub fn random<R: Rng>(rng: &mut R) -> Self {
|
||||
TraceId((rng.next_u64() as u128) << mem::size_of::<u64>() | rng.next_u64() as u128)
|
||||
}
|
||||
}
|
||||
|
||||
impl SpanId {
|
||||
/// Returns a random span ID that can be assumed to be unique within a single trace.
|
||||
pub fn random<R: Rng>(rng: &mut R) -> Self {
|
||||
SpanId(rng.next_u64())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TraceId {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
||||
write!(f, "{:02x}", self.0)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SpanId {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
||||
write!(f, "{:02x}", self.0)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user