mirror of
https://github.com/OMGeeky/tarpc.git
synced 2026-02-23 15:49:54 +01:00
Merge pull request #240 from tikue/filter-refactor
This commit is contained in:
@@ -175,7 +175,7 @@ async fn run() -> io::Result<()> {
|
|||||||
let server = server::new(server::Config::default())
|
let server = server::new(server::Config::default())
|
||||||
// incoming() takes a stream of transports such as would be returned by
|
// incoming() takes a stream of transports such as would be returned by
|
||||||
// TcpListener::incoming (but a stream instead of an iterator).
|
// TcpListener::incoming (but a stream instead of an iterator).
|
||||||
.incoming(stream::once(future::ready(Ok(server_transport))))
|
.incoming(stream::once(future::ready(server_transport)))
|
||||||
// serve is generated by the service! macro. It takes as input any type implementing
|
// serve is generated by the service! macro. It takes as input any type implementing
|
||||||
// the generated Service trait.
|
// the generated Service trait.
|
||||||
.respond_with(serve(HelloServer));
|
.respond_with(serve(HelloServer));
|
||||||
@@ -246,7 +246,7 @@ background tasks for the client and server.
|
|||||||
# let server = server::new(server::Config::default())
|
# let server = server::new(server::Config::default())
|
||||||
# // incoming() takes a stream of transports such as would be returned by
|
# // incoming() takes a stream of transports such as would be returned by
|
||||||
# // TcpListener::incoming (but a stream instead of an iterator).
|
# // TcpListener::incoming (but a stream instead of an iterator).
|
||||||
# .incoming(stream::once(future::ready(Ok(server_transport))))
|
# .incoming(stream::once(future::ready(server_transport)))
|
||||||
# // serve is generated by the service! macro. It takes as input any type implementing
|
# // serve is generated by the service! macro. It takes as input any type implementing
|
||||||
# // the generated Service trait.
|
# // the generated Service trait.
|
||||||
# .respond_with(serve(HelloServer));
|
# .respond_with(serve(HelloServer));
|
||||||
|
|||||||
@@ -17,19 +17,11 @@ bincode = "1"
|
|||||||
futures-preview = { version = "0.3.0-alpha.17", features = ["compat"] }
|
futures-preview = { version = "0.3.0-alpha.17", features = ["compat"] }
|
||||||
futures_legacy = { version = "0.1", package = "futures" }
|
futures_legacy = { version = "0.1", package = "futures" }
|
||||||
pin-utils = "0.1.0-alpha.4"
|
pin-utils = "0.1.0-alpha.4"
|
||||||
rpc = { package = "tarpc-lib", version = "0.6", path = "../rpc", features = ["serde1"] }
|
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
tokio-io = "0.1"
|
tokio-io = "0.1"
|
||||||
async-bincode = "0.4"
|
async-bincode = "0.4"
|
||||||
tokio-tcp = "0.1"
|
tokio-tcp = "0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.6"
|
futures-test-preview = { version = "0.3.0-alpha.17" }
|
||||||
humantime = "1.0"
|
assert_matches = "1.0"
|
||||||
log = "0.4"
|
|
||||||
rand = "0.6"
|
|
||||||
tokio = "0.1"
|
|
||||||
tokio-executor = "0.1"
|
|
||||||
tokio-reactor = "0.1"
|
|
||||||
tokio-serde = "0.3"
|
|
||||||
tokio-timer = "0.2"
|
|
||||||
|
|||||||
@@ -81,7 +81,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert<E: Into<Box<dyn Error + Send + Sync>>>(poll: Poll<Result<(), E>>) -> Poll<io::Result<()>> {
|
fn convert<E: Into<Box<dyn Error + Send + Sync>>>(
|
||||||
|
poll: Poll<Result<(), E>>,
|
||||||
|
) -> Poll<io::Result<()>> {
|
||||||
match poll {
|
match poll {
|
||||||
Poll::Pending => Poll::Pending,
|
Poll::Pending => Poll::Pending,
|
||||||
Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
|
Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
|
||||||
@@ -89,23 +91,24 @@ fn convert<E: Into<Box<dyn Error + Send + Sync>>>(poll: Poll<Result<(), E>>) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Item, SinkItem> rpc::Transport for Transport<TcpStream, Item, SinkItem>
|
impl<Item, SinkItem> Transport<TcpStream, Item, SinkItem> {
|
||||||
where
|
/// Returns the address of the peer connected over the transport.
|
||||||
Item: for<'de> Deserialize<'de>,
|
pub fn peer_addr(&self) -> io::Result<SocketAddr> {
|
||||||
SinkItem: Serialize,
|
|
||||||
{
|
|
||||||
type Item = Item;
|
|
||||||
type SinkItem = SinkItem;
|
|
||||||
|
|
||||||
fn peer_addr(&self) -> io::Result<SocketAddr> {
|
|
||||||
self.inner.get_ref().get_ref().peer_addr()
|
self.inner.get_ref().get_ref().peer_addr()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn local_addr(&self) -> io::Result<SocketAddr> {
|
/// Returns the address of this end of the transport.
|
||||||
|
pub fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||||
self.inner.get_ref().get_ref().local_addr()
|
self.inner.get_ref().get_ref().local_addr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T, Item, SinkItem> AsRef<T> for Transport<T, Item, SinkItem> {
|
||||||
|
fn as_ref(&self) -> &T {
|
||||||
|
self.inner.get_ref().get_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a new bincode transport that reads from and writes to `io`.
|
/// Returns a new bincode transport that reads from and writes to `io`.
|
||||||
pub fn new<Item, SinkItem>(io: TcpStream) -> Transport<TcpStream, Item, SinkItem>
|
pub fn new<Item, SinkItem>(io: TcpStream) -> Transport<TcpStream, Item, SinkItem>
|
||||||
where
|
where
|
||||||
@@ -179,3 +182,50 @@ where
|
|||||||
Poll::Ready(next.map(|conn| Ok(new(conn))))
|
Poll::Ready(next.map(|conn| Ok(new(conn))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Transport;
|
||||||
|
use assert_matches::assert_matches;
|
||||||
|
use futures::{Sink, Stream};
|
||||||
|
use futures_test::task::noop_waker_ref;
|
||||||
|
use pin_utils::pin_mut;
|
||||||
|
use std::{
|
||||||
|
io::Cursor,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn ctx() -> Context<'static> {
|
||||||
|
Context::from_waker(&noop_waker_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stream() {
|
||||||
|
// Frame is big endian; bincode is little endian. A bit confusing!
|
||||||
|
let reader = *b"\x00\x00\x00\x1e\x16\x00\x00\x00\x00\x00\x00\x00Test one, check check.";
|
||||||
|
let reader: Box<[u8]> = Box::new(reader);
|
||||||
|
let transport = Transport::<_, String, String>::from(Cursor::new(reader));
|
||||||
|
pin_mut!(transport);
|
||||||
|
|
||||||
|
assert_matches!(
|
||||||
|
transport.poll_next(&mut ctx()),
|
||||||
|
Poll::Ready(Some(Ok(ref s))) if s == "Test one, check check.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sink() {
|
||||||
|
let writer: &mut [u8] = &mut [0; 34];
|
||||||
|
let transport = Transport::<_, String, String>::from(Cursor::new(&mut *writer));
|
||||||
|
pin_mut!(transport);
|
||||||
|
|
||||||
|
assert_matches!(transport.as_mut().poll_ready(&mut ctx()), Poll::Ready(Ok(())));
|
||||||
|
assert_matches!(transport .as_mut() .start_send("Test one, check check.".into()), Ok(()));
|
||||||
|
assert_matches!(transport.poll_flush(&mut ctx()), Poll::Ready(Ok(())));
|
||||||
|
assert_eq!(
|
||||||
|
writer,
|
||||||
|
<&[u8]>::from(
|
||||||
|
b"\x00\x00\x00\x1e\x16\x00\x00\x00\x00\x00\x00\x00Test one, check check."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
// 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, async_await)]
|
|
||||||
|
|
||||||
extern crate test;
|
|
||||||
|
|
||||||
use futures::{compat::Executor01CompatExt, prelude::*};
|
|
||||||
use test::stats::Stats;
|
|
||||||
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 = tarpc_bincode_transport::connect(&addr).await?;
|
|
||||||
let client = &mut client::new::<u32, u32, _>(client::Config::default(), conn).await?;
|
|
||||||
|
|
||||||
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 = client.call(context::current(), 0u32).await;
|
|
||||||
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(tokio::executor::DefaultExecutor::current().compat());
|
|
||||||
|
|
||||||
tokio::run(bench().map_err(|e| panic!(e.to_string())).boxed().compat());
|
|
||||||
println!("done");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
// 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(async_await)]
|
|
||||||
#![feature(async_closure)]
|
|
||||||
|
|
||||||
use futures::{
|
|
||||||
compat::{Executor01CompatExt, Future01CompatExt},
|
|
||||||
prelude::*,
|
|
||||||
stream::FuturesUnordered,
|
|
||||||
};
|
|
||||||
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 {
|
|
||||||
wait.await.unwrap();
|
|
||||||
Ok(request)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tokio_executor::spawn(handler.unit_error().boxed().compat());
|
|
||||||
});
|
|
||||||
|
|
||||||
tokio_executor::spawn(server.unit_error().boxed().compat());
|
|
||||||
|
|
||||||
let conn = tarpc_bincode_transport::connect(&addr).await?;
|
|
||||||
let client = client::new::<String, String, _>(client::Config::default(), conn).await?;
|
|
||||||
|
|
||||||
// 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 { client.call(ctx, request).await }
|
|
||||||
});
|
|
||||||
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 =
|
|
||||||
client::new::<String, String, _>(config, tarpc_bincode_transport::connect(&addr).await?)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// 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, _) = requests
|
|
||||||
.into_iter()
|
|
||||||
.collect::<FuturesUnordered<_>>()
|
|
||||||
.into_future()
|
|
||||||
.await;
|
|
||||||
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(tokio::executor::DefaultExecutor::current().compat());
|
|
||||||
|
|
||||||
tokio::run(run().boxed().map_err(|e| panic!(e)).compat());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
// 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(async_await)]
|
|
||||||
#![feature(async_closure)]
|
|
||||||
|
|
||||||
use futures::{
|
|
||||||
compat::{Executor01CompatExt, Future01CompatExt},
|
|
||||||
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 {
|
|
||||||
sleep.await.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 = tarpc_bincode_transport::connect(&addr).await?;
|
|
||||||
let client = client::new::<String, String, _>(config, conn).await?;
|
|
||||||
|
|
||||||
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 response.await {
|
|
||||||
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(tokio::executor::DefaultExecutor::current().compat());
|
|
||||||
|
|
||||||
tokio::run(
|
|
||||||
run()
|
|
||||||
.map_ok(|_| println!("done"))
|
|
||||||
.map_err(|e| panic!(e.to_string()))
|
|
||||||
.boxed()
|
|
||||||
.compat(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -13,12 +13,13 @@ readme = "../README.md"
|
|||||||
description = "An example server built on tarpc."
|
description = "An example server built on tarpc."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode-transport = { package = "tarpc-bincode-transport", version = "0.7", path = "../bincode-transport" }
|
json-transport = { package = "tarpc-json-transport", version = "0.1", path = "../json-transport" }
|
||||||
clap = "2.0"
|
clap = "2.0"
|
||||||
futures-preview = { version = "0.3.0-alpha.17", features = ["compat"] }
|
futures-preview = { version = "0.3.0-alpha.17", features = ["compat"] }
|
||||||
serde = { version = "1.0" }
|
serde = { version = "1.0" }
|
||||||
tarpc = { version = "0.18", path = "../tarpc", features = ["serde1"] }
|
tarpc = { version = "0.18", path = "../tarpc", features = ["serde1"] }
|
||||||
tokio = "0.1"
|
tokio = "0.1"
|
||||||
|
env_logger = "0.6"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "service"
|
name = "service"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use std::{io, net::SocketAddr};
|
|||||||
use tarpc::{client, context};
|
use tarpc::{client, context};
|
||||||
|
|
||||||
async fn run(server_addr: SocketAddr, name: String) -> io::Result<()> {
|
async fn run(server_addr: SocketAddr, name: String) -> io::Result<()> {
|
||||||
let transport = bincode_transport::connect(&server_addr).await?;
|
let transport = json_transport::connect(&server_addr).await?;
|
||||||
|
|
||||||
// new_stub is generated by the service! macro. Like Server, it takes a config and any
|
// 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.
|
// Transport as input, and returns a Client, also generated by the macro.
|
||||||
|
|||||||
@@ -10,5 +10,9 @@
|
|||||||
// It defines one RPC, hello, which takes one arg, name, and returns a String.
|
// It defines one RPC, hello, which takes one arg, name, and returns a String.
|
||||||
tarpc::service! {
|
tarpc::service! {
|
||||||
/// Returns a greeting for name.
|
/// Returns a greeting for name.
|
||||||
rpc hello(name: String) -> String;
|
rpc hello(#[serde(default = "default_name")] name: String) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_name() -> String {
|
||||||
|
"DefaultName".into()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ use futures::{
|
|||||||
use std::{io, net::SocketAddr};
|
use std::{io, net::SocketAddr};
|
||||||
use tarpc::{
|
use tarpc::{
|
||||||
context,
|
context,
|
||||||
server::{Handler, Server},
|
server::{self, Channel, Handler},
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is the type that implements the generated Service trait. It is the business logic
|
// This is the type that implements the generated Service trait. It is the business logic
|
||||||
// and is used to start the server.
|
// and is used to start the server.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct HelloServer;
|
struct HelloServer(SocketAddr);
|
||||||
|
|
||||||
impl service::Service for HelloServer {
|
impl service::Service for HelloServer {
|
||||||
// Each defined rpc generates two items in the trait, a fn that serves the RPC, and
|
// Each defined rpc generates two items in the trait, a fn that serves the RPC, and
|
||||||
@@ -30,29 +30,39 @@ impl service::Service for HelloServer {
|
|||||||
type HelloFut = Ready<String>;
|
type HelloFut = Ready<String>;
|
||||||
|
|
||||||
fn hello(self, _: context::Context, name: String) -> Self::HelloFut {
|
fn hello(self, _: context::Context, name: String) -> Self::HelloFut {
|
||||||
future::ready(format!("Hello, {}!", name))
|
future::ready(format!(
|
||||||
|
"Hello, {}! You are connected from {:?}.",
|
||||||
|
name, self.0
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run(server_addr: SocketAddr) -> io::Result<()> {
|
async fn run(server_addr: SocketAddr) -> io::Result<()> {
|
||||||
// bincode_transport is provided by the associated crate bincode-transport. It makes it easy
|
// 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.
|
// to start up a serde-powered bincode serialization strategy over TCP.
|
||||||
let transport = bincode_transport::listen(&server_addr)?;
|
json_transport::listen(&server_addr)?
|
||||||
|
// Ignore accept errors.
|
||||||
// The server is configured with the defaults.
|
.filter_map(|r| future::ready(r.ok()))
|
||||||
let server = Server::default()
|
.map(server::BaseChannel::with_defaults)
|
||||||
// Server can listen on any type that implements the Transport trait.
|
// Limit channels to 1 per IP.
|
||||||
.incoming(transport)
|
.max_channels_per_key(1, |t| t.as_ref().peer_addr().unwrap().ip())
|
||||||
// serve is generated by the service! macro. It takes as input any type implementing
|
// serve is generated by the service! macro. It takes as input any type implementing
|
||||||
// the generated Service trait.
|
// the generated Service trait.
|
||||||
.respond_with(service::serve(HelloServer));
|
.map(|channel| {
|
||||||
|
let server = HelloServer(channel.as_ref().as_ref().peer_addr().unwrap());
|
||||||
server.await;
|
channel.respond_with(service::serve(server))
|
||||||
|
})
|
||||||
|
// Max 10 channels.
|
||||||
|
.buffer_unordered(10)
|
||||||
|
.for_each(|_| futures::future::ready(()))
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
let flags = App::new("Hello Server")
|
let flags = App::new("Hello Server")
|
||||||
.version("0.1")
|
.version("0.1")
|
||||||
.author("Tim <tikue@google.com>")
|
.author("Tim <tikue@google.com>")
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ description = "A JSON-based transport for tarpc services."
|
|||||||
futures-preview = { version = "0.3.0-alpha.17", features = ["compat"] }
|
futures-preview = { version = "0.3.0-alpha.17", features = ["compat"] }
|
||||||
futures_legacy = { version = "0.1", package = "futures" }
|
futures_legacy = { version = "0.1", package = "futures" }
|
||||||
pin-utils = "0.1.0-alpha.4"
|
pin-utils = "0.1.0-alpha.4"
|
||||||
rpc = { package = "tarpc-lib", version = "0.6", path = "../rpc", features = ["serde1"] }
|
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tokio = "0.1"
|
tokio = "0.1"
|
||||||
@@ -25,12 +24,5 @@ tokio-serde-json = "0.2"
|
|||||||
tokio-tcp = "0.1"
|
tokio-tcp = "0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.6"
|
futures-test-preview = { version = "0.3.0-alpha.17" }
|
||||||
humantime = "1.0"
|
assert_matches = "1.0"
|
||||||
log = "0.4"
|
|
||||||
rand = "0.6"
|
|
||||||
tokio = "0.1"
|
|
||||||
tokio-executor = "0.1"
|
|
||||||
tokio-reactor = "0.1"
|
|
||||||
tokio-serde = "0.3"
|
|
||||||
tokio-timer = "0.2"
|
|
||||||
|
|||||||
@@ -88,7 +88,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert<E: Into<Box<dyn Error + Send + Sync>>>(poll: Poll<Result<(), E>>) -> Poll<io::Result<()>> {
|
fn convert<E: Into<Box<dyn Error + Send + Sync>>>(
|
||||||
|
poll: Poll<Result<(), E>>,
|
||||||
|
) -> Poll<io::Result<()>> {
|
||||||
match poll {
|
match poll {
|
||||||
Poll::Pending => Poll::Pending,
|
Poll::Pending => Poll::Pending,
|
||||||
Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
|
Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
|
||||||
@@ -96,15 +98,9 @@ fn convert<E: Into<Box<dyn Error + Send + Sync>>>(poll: Poll<Result<(), E>>) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Item, SinkItem> rpc::Transport for Transport<TcpStream, Item, SinkItem>
|
impl<Item, SinkItem> Transport<TcpStream, Item, SinkItem> {
|
||||||
where
|
/// Returns the peer address of the underlying TcpStream.
|
||||||
Item: for<'de> Deserialize<'de>,
|
pub fn peer_addr(&self) -> io::Result<SocketAddr> {
|
||||||
SinkItem: Serialize,
|
|
||||||
{
|
|
||||||
type Item = Item;
|
|
||||||
type SinkItem = SinkItem;
|
|
||||||
|
|
||||||
fn peer_addr(&self) -> io::Result<SocketAddr> {
|
|
||||||
self.inner
|
self.inner
|
||||||
.get_ref()
|
.get_ref()
|
||||||
.get_ref()
|
.get_ref()
|
||||||
@@ -113,7 +109,8 @@ where
|
|||||||
.peer_addr()
|
.peer_addr()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn local_addr(&self) -> io::Result<SocketAddr> {
|
/// Returns the local address of the underlying TcpStream.
|
||||||
|
pub fn local_addr(&self) -> io::Result<SocketAddr> {
|
||||||
self.inner
|
self.inner
|
||||||
.get_ref()
|
.get_ref()
|
||||||
.get_ref()
|
.get_ref()
|
||||||
@@ -201,3 +198,41 @@ where
|
|||||||
Poll::Ready(next.map(|conn| Ok(new(conn))))
|
Poll::Ready(next.map(|conn| Ok(new(conn))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use assert_matches::assert_matches;
|
||||||
|
use futures::{Sink, Stream};
|
||||||
|
use futures_test::task::noop_waker_ref;
|
||||||
|
use pin_utils::pin_mut;
|
||||||
|
use std::{io::Cursor, task::{Context, Poll}};
|
||||||
|
use super::Transport;
|
||||||
|
|
||||||
|
fn ctx() -> Context<'static> {
|
||||||
|
Context::from_waker(&noop_waker_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stream() {
|
||||||
|
let reader = *b"\x00\x00\x00\x18\"Test one, check check.\"";
|
||||||
|
let reader: Box<[u8]> = Box::new(reader);
|
||||||
|
let transport = Transport::<_, String, String>::from(Cursor::new(reader));
|
||||||
|
pin_mut!(transport);
|
||||||
|
|
||||||
|
assert_matches!(
|
||||||
|
transport.poll_next(&mut ctx()),
|
||||||
|
Poll::Ready(Some(Ok(ref s))) if s == "Test one, check check.");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sink() {
|
||||||
|
let writer: &mut [u8] = &mut [0; 28];
|
||||||
|
let transport = Transport::<_, String, String>::from(Cursor::new(&mut *writer));
|
||||||
|
pin_mut!(transport);
|
||||||
|
|
||||||
|
assert_matches!(transport.as_mut().poll_ready(&mut ctx()), Poll::Ready(Ok(())));
|
||||||
|
assert_matches!(transport.as_mut().start_send("Test one, check check.".into()), Ok(()));
|
||||||
|
assert_matches!(transport.poll_flush(&mut ctx()), Poll::Ready(Ok(())));
|
||||||
|
assert_eq!(writer, b"\x00\x00\x00\x18\"Test one, check check.\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
// Copyright 2019 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, async_await)]
|
|
||||||
|
|
||||||
extern crate test;
|
|
||||||
|
|
||||||
use futures::{compat::Executor01CompatExt, prelude::*};
|
|
||||||
use test::stats::Stats;
|
|
||||||
use rpc::{
|
|
||||||
client, context,
|
|
||||||
server::{Handler, Server},
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
io,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
async fn bench() -> io::Result<()> {
|
|
||||||
let listener = tarpc_json_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 = tarpc_json_transport::connect(&addr).await?;
|
|
||||||
let client = &mut client::new::<u32, u32, _>(client::Config::default(), conn).await?;
|
|
||||||
|
|
||||||
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 = client.call(context::current(), 0u32).await;
|
|
||||||
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(tokio::executor::DefaultExecutor::current().compat());
|
|
||||||
|
|
||||||
tokio::run(bench().map_err(|e| panic!(e.to_string())).boxed().compat());
|
|
||||||
println!("done");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
// Copyright 2019 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(async_await)]
|
|
||||||
#![feature(async_closure)]
|
|
||||||
|
|
||||||
use futures::{
|
|
||||||
compat::{Executor01CompatExt, Future01CompatExt},
|
|
||||||
prelude::*,
|
|
||||||
stream::FuturesUnordered,
|
|
||||||
};
|
|
||||||
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_json_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 {
|
|
||||||
wait.await.unwrap();
|
|
||||||
Ok(request)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tokio_executor::spawn(handler.unit_error().boxed().compat());
|
|
||||||
});
|
|
||||||
|
|
||||||
tokio_executor::spawn(server.unit_error().boxed().compat());
|
|
||||||
|
|
||||||
let conn = tarpc_json_transport::connect(&addr).await?;
|
|
||||||
let client = client::new::<String, String, _>(client::Config::default(), conn).await?;
|
|
||||||
|
|
||||||
// Proxy service
|
|
||||||
let listener = tarpc_json_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 { client.call(ctx, request).await }
|
|
||||||
});
|
|
||||||
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 =
|
|
||||||
client::new::<String, String, _>(config, tarpc_json_transport::connect(&addr).await?)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// 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, _) = requests
|
|
||||||
.into_iter()
|
|
||||||
.collect::<FuturesUnordered<_>>()
|
|
||||||
.into_future()
|
|
||||||
.await;
|
|
||||||
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(tokio::executor::DefaultExecutor::current().compat());
|
|
||||||
|
|
||||||
tokio::run(run().boxed().map_err(|e| panic!(e)).compat());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
// Copyright 2019 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(async_await)]
|
|
||||||
#![feature(async_closure)]
|
|
||||||
|
|
||||||
use futures::{
|
|
||||||
compat::{Executor01CompatExt, Future01CompatExt},
|
|
||||||
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_json_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 {
|
|
||||||
sleep.await.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 = tarpc_json_transport::connect(&addr).await?;
|
|
||||||
let client = client::new::<String, String, _>(config, conn).await?;
|
|
||||||
|
|
||||||
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 response.await {
|
|
||||||
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(tokio::executor::DefaultExecutor::current().compat());
|
|
||||||
|
|
||||||
tokio::run(
|
|
||||||
run()
|
|
||||||
.map_ok(|_| println!("done"))
|
|
||||||
.map_err(|e| panic!(e.to_string()))
|
|
||||||
.boxed()
|
|
||||||
.compat(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -21,8 +21,9 @@ fnv = "1.0"
|
|||||||
futures-preview = { version = "0.3.0-alpha.17", features = ["compat"] }
|
futures-preview = { version = "0.3.0-alpha.17", features = ["compat"] }
|
||||||
humantime = "1.0"
|
humantime = "1.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
once_cell = "0.2"
|
||||||
pin-utils = "0.1.0-alpha.4"
|
pin-utils = "0.1.0-alpha.4"
|
||||||
rand = "0.6"
|
rand = "0.7"
|
||||||
tokio-timer = "0.2"
|
tokio-timer = "0.2"
|
||||||
trace = { package = "tarpc-trace", version = "0.2", path = "../trace" }
|
trace = { package = "tarpc-trace", version = "0.2", path = "../trace" }
|
||||||
serde = { optional = true, version = "1.0" }
|
serde = { optional = true, version = "1.0" }
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
context,
|
context,
|
||||||
util::{deadline_compat, AsDuration, Compact},
|
util::{deadline_compat, AsDuration, Compact},
|
||||||
ClientMessage, ClientMessageKind, PollIo, Request, Response, Transport,
|
ClientMessage, PollIo, Request, Response, Transport,
|
||||||
};
|
};
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use futures::{
|
use futures::{
|
||||||
@@ -24,7 +24,6 @@ use pin_utils::{unsafe_pinned, unsafe_unpinned};
|
|||||||
use std::{
|
use std::{
|
||||||
io,
|
io,
|
||||||
marker::{self, Unpin},
|
marker::{self, Unpin},
|
||||||
net::SocketAddr,
|
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicU64, Ordering},
|
atomic::{AtomicU64, Ordering},
|
||||||
@@ -44,7 +43,6 @@ pub struct Channel<Req, Resp> {
|
|||||||
cancellation: RequestCancellation,
|
cancellation: RequestCancellation,
|
||||||
/// The ID to use for the next request to stage.
|
/// The ID to use for the next request to stage.
|
||||||
next_request_id: Arc<AtomicU64>,
|
next_request_id: Arc<AtomicU64>,
|
||||||
server_addr: SocketAddr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Req, Resp> Clone for Channel<Req, Resp> {
|
impl<Req, Resp> Clone for Channel<Req, Resp> {
|
||||||
@@ -53,7 +51,6 @@ impl<Req, Resp> Clone for Channel<Req, Resp> {
|
|||||||
to_dispatch: self.to_dispatch.clone(),
|
to_dispatch: self.to_dispatch.clone(),
|
||||||
cancellation: self.cancellation.clone(),
|
cancellation: self.cancellation.clone(),
|
||||||
next_request_id: self.next_request_id.clone(),
|
next_request_id: self.next_request_id.clone(),
|
||||||
server_addr: self.server_addr,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,9 +119,8 @@ impl<Req, Resp> Channel<Req, Resp> {
|
|||||||
let timeout = ctx.deadline.as_duration();
|
let timeout = ctx.deadline.as_duration();
|
||||||
let deadline = Instant::now() + timeout;
|
let deadline = Instant::now() + timeout;
|
||||||
trace!(
|
trace!(
|
||||||
"[{}/{}] Queuing request with deadline {} (timeout {:?}).",
|
"[{}] Queuing request with deadline {} (timeout {:?}).",
|
||||||
ctx.trace_id(),
|
ctx.trace_id(),
|
||||||
self.server_addr,
|
|
||||||
format_rfc3339(ctx.deadline),
|
format_rfc3339(ctx.deadline),
|
||||||
timeout,
|
timeout,
|
||||||
);
|
);
|
||||||
@@ -132,7 +128,6 @@ impl<Req, Resp> Channel<Req, Resp> {
|
|||||||
let (response_completion, response) = oneshot::channel();
|
let (response_completion, response) = oneshot::channel();
|
||||||
let cancellation = self.cancellation.clone();
|
let cancellation = self.cancellation.clone();
|
||||||
let request_id = self.next_request_id.fetch_add(1, Ordering::Relaxed);
|
let request_id = self.next_request_id.fetch_add(1, Ordering::Relaxed);
|
||||||
let server_addr = self.server_addr;
|
|
||||||
Send {
|
Send {
|
||||||
fut: MapOkDispatchResponse::new(
|
fut: MapOkDispatchResponse::new(
|
||||||
MapErrConnectionReset::new(self.to_dispatch.send(DispatchRequest {
|
MapErrConnectionReset::new(self.to_dispatch.send(DispatchRequest {
|
||||||
@@ -147,7 +142,6 @@ impl<Req, Resp> Channel<Req, Resp> {
|
|||||||
request_id,
|
request_id,
|
||||||
cancellation,
|
cancellation,
|
||||||
ctx,
|
ctx,
|
||||||
server_addr,
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -171,11 +165,9 @@ struct DispatchResponse<Resp> {
|
|||||||
complete: bool,
|
complete: bool,
|
||||||
cancellation: RequestCancellation,
|
cancellation: RequestCancellation,
|
||||||
request_id: u64,
|
request_id: u64,
|
||||||
server_addr: SocketAddr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Resp> DispatchResponse<Resp> {
|
impl<Resp> DispatchResponse<Resp> {
|
||||||
unsafe_pinned!(server_addr: SocketAddr);
|
|
||||||
unsafe_pinned!(ctx: context::Context);
|
unsafe_pinned!(ctx: context::Context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +184,6 @@ impl<Resp> Future for DispatchResponse<Resp> {
|
|||||||
}
|
}
|
||||||
Err(e) => Err({
|
Err(e) => Err({
|
||||||
let trace_id = *self.as_mut().ctx().trace_id();
|
let trace_id = *self.as_mut().ctx().trace_id();
|
||||||
let server_addr = *self.as_mut().server_addr();
|
|
||||||
|
|
||||||
if e.is_elapsed() {
|
if e.is_elapsed() {
|
||||||
io::Error::new(
|
io::Error::new(
|
||||||
@@ -209,12 +200,9 @@ impl<Resp> Future for DispatchResponse<Resp> {
|
|||||||
.to_string(),
|
.to_string(),
|
||||||
)
|
)
|
||||||
} else if e.is_shutdown() {
|
} else if e.is_shutdown() {
|
||||||
panic!("[{}/{}] Timer was shutdown", trace_id, server_addr)
|
panic!("[{}] Timer was shutdown", trace_id)
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
panic!("[{}] Unrecognized timer error: {}", trace_id, e)
|
||||||
"[{}/{}] Unrecognized timer error: {}",
|
|
||||||
trace_id, server_addr, e
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else if e.is_inner() {
|
} else if e.is_inner() {
|
||||||
// The oneshot is Canceled when the dispatch task ends. In that case,
|
// The oneshot is Canceled when the dispatch task ends. In that case,
|
||||||
@@ -223,10 +211,7 @@ impl<Resp> Future for DispatchResponse<Resp> {
|
|||||||
self.complete = true;
|
self.complete = true;
|
||||||
io::Error::from(io::ErrorKind::ConnectionReset)
|
io::Error::from(io::ErrorKind::ConnectionReset)
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
panic!("[{}] Unrecognized deadline error: {}", trace_id, e)
|
||||||
"[{}/{}] Unrecognized deadline error: {}",
|
|
||||||
trace_id, server_addr, e
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -255,15 +240,11 @@ impl<Resp> Drop for DispatchResponse<Resp> {
|
|||||||
|
|
||||||
/// Spawns a dispatch task on the default executor that manages the lifecycle of requests initiated
|
/// Spawns a dispatch task on the default executor that manages the lifecycle of requests initiated
|
||||||
/// by the returned [`Channel`].
|
/// by the returned [`Channel`].
|
||||||
pub async fn spawn<Req, Resp, C>(
|
pub async fn spawn<Req, Resp, C>(config: Config, transport: C) -> io::Result<Channel<Req, Resp>>
|
||||||
config: Config,
|
|
||||||
transport: C,
|
|
||||||
server_addr: SocketAddr,
|
|
||||||
) -> io::Result<Channel<Req, Resp>>
|
|
||||||
where
|
where
|
||||||
Req: marker::Send + 'static,
|
Req: marker::Send + 'static,
|
||||||
Resp: marker::Send + 'static,
|
Resp: marker::Send + 'static,
|
||||||
C: Transport<Item = Response<Resp>, SinkItem = ClientMessage<Req>> + marker::Send + 'static,
|
C: Transport<ClientMessage<Req>, Response<Resp>> + marker::Send + 'static,
|
||||||
{
|
{
|
||||||
let (to_dispatch, pending_requests) = mpsc::channel(config.pending_request_buffer);
|
let (to_dispatch, pending_requests) = mpsc::channel(config.pending_request_buffer);
|
||||||
let (cancellation, canceled_requests) = cancellations();
|
let (cancellation, canceled_requests) = cancellations();
|
||||||
@@ -272,13 +253,12 @@ where
|
|||||||
crate::spawn(
|
crate::spawn(
|
||||||
RequestDispatch {
|
RequestDispatch {
|
||||||
config,
|
config,
|
||||||
server_addr,
|
|
||||||
canceled_requests,
|
canceled_requests,
|
||||||
transport: transport.fuse(),
|
transport: transport.fuse(),
|
||||||
in_flight_requests: FnvHashMap::default(),
|
in_flight_requests: FnvHashMap::default(),
|
||||||
pending_requests: pending_requests.fuse(),
|
pending_requests: pending_requests.fuse(),
|
||||||
}
|
}
|
||||||
.unwrap_or_else(move |e| error!("[{}] Connection broken: {}", server_addr, e)),
|
.unwrap_or_else(move |e| error!("Connection broken: {}", e)),
|
||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
io::Error::new(
|
io::Error::new(
|
||||||
@@ -293,7 +273,6 @@ where
|
|||||||
Ok(Channel {
|
Ok(Channel {
|
||||||
to_dispatch,
|
to_dispatch,
|
||||||
cancellation,
|
cancellation,
|
||||||
server_addr,
|
|
||||||
next_request_id: Arc::new(AtomicU64::new(0)),
|
next_request_id: Arc::new(AtomicU64::new(0)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -311,17 +290,14 @@ struct RequestDispatch<Req, Resp, C> {
|
|||||||
in_flight_requests: FnvHashMap<u64, InFlightData<Resp>>,
|
in_flight_requests: FnvHashMap<u64, InFlightData<Resp>>,
|
||||||
/// Configures limits to prevent unlimited resource usage.
|
/// Configures limits to prevent unlimited resource usage.
|
||||||
config: Config,
|
config: Config,
|
||||||
/// The address of the server connected to.
|
|
||||||
server_addr: SocketAddr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Req, Resp, C> RequestDispatch<Req, Resp, C>
|
impl<Req, Resp, C> RequestDispatch<Req, Resp, C>
|
||||||
where
|
where
|
||||||
Req: marker::Send,
|
Req: marker::Send,
|
||||||
Resp: marker::Send,
|
Resp: marker::Send,
|
||||||
C: Transport<Item = Response<Resp>, SinkItem = ClientMessage<Req>>,
|
C: Transport<ClientMessage<Req>, Response<Resp>>,
|
||||||
{
|
{
|
||||||
unsafe_pinned!(server_addr: SocketAddr);
|
|
||||||
unsafe_pinned!(in_flight_requests: FnvHashMap<u64, InFlightData<Resp>>);
|
unsafe_pinned!(in_flight_requests: FnvHashMap<u64, InFlightData<Resp>>);
|
||||||
unsafe_pinned!(canceled_requests: Fuse<CanceledRequests>);
|
unsafe_pinned!(canceled_requests: Fuse<CanceledRequests>);
|
||||||
unsafe_pinned!(pending_requests: Fuse<mpsc::Receiver<DispatchRequest<Req, Resp>>>);
|
unsafe_pinned!(pending_requests: Fuse<mpsc::Receiver<DispatchRequest<Req, Resp>>>);
|
||||||
@@ -333,10 +309,7 @@ where
|
|||||||
self.complete(response);
|
self.complete(response);
|
||||||
Some(Ok(()))
|
Some(Ok(()))
|
||||||
}
|
}
|
||||||
None => {
|
None => None,
|
||||||
trace!("[{}] read half closed", self.as_mut().server_addr());
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,10 +388,7 @@ where
|
|||||||
|
|
||||||
return Poll::Ready(Some(Ok(request)));
|
return Poll::Ready(Some(Ok(request)));
|
||||||
}
|
}
|
||||||
None => {
|
None => return Poll::Ready(None),
|
||||||
trace!("[{}] pending_requests closed", self.as_mut().server_addr());
|
|
||||||
return Poll::Ready(None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -440,23 +410,11 @@ where
|
|||||||
self.as_mut().in_flight_requests().remove(&request_id)
|
self.as_mut().in_flight_requests().remove(&request_id)
|
||||||
{
|
{
|
||||||
self.as_mut().in_flight_requests().compact(0.1);
|
self.as_mut().in_flight_requests().compact(0.1);
|
||||||
|
debug!("[{}] Removed request.", in_flight_data.ctx.trace_id());
|
||||||
debug!(
|
|
||||||
"[{}/{}] Removed request.",
|
|
||||||
in_flight_data.ctx.trace_id(),
|
|
||||||
self.as_mut().server_addr()
|
|
||||||
);
|
|
||||||
|
|
||||||
return Poll::Ready(Some(Ok((in_flight_data.ctx, request_id))));
|
return Poll::Ready(Some(Ok((in_flight_data.ctx, request_id))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => return Poll::Ready(None),
|
||||||
trace!(
|
|
||||||
"[{}] canceled_requests closed.",
|
|
||||||
self.as_mut().server_addr()
|
|
||||||
);
|
|
||||||
return Poll::Ready(None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,14 +424,14 @@ where
|
|||||||
dispatch_request: DispatchRequest<Req, Resp>,
|
dispatch_request: DispatchRequest<Req, Resp>,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
let request_id = dispatch_request.request_id;
|
let request_id = dispatch_request.request_id;
|
||||||
let request = ClientMessage {
|
let request = ClientMessage::Request(Request {
|
||||||
trace_context: dispatch_request.ctx.trace_context,
|
id: request_id,
|
||||||
message: ClientMessageKind::Request(Request {
|
message: dispatch_request.request,
|
||||||
id: request_id,
|
context: context::Context {
|
||||||
message: dispatch_request.request,
|
|
||||||
deadline: dispatch_request.ctx.deadline,
|
deadline: dispatch_request.ctx.deadline,
|
||||||
}),
|
trace_context: dispatch_request.ctx.trace_context,
|
||||||
};
|
},
|
||||||
|
});
|
||||||
self.as_mut().transport().start_send(request)?;
|
self.as_mut().transport().start_send(request)?;
|
||||||
self.as_mut().in_flight_requests().insert(
|
self.as_mut().in_flight_requests().insert(
|
||||||
request_id,
|
request_id,
|
||||||
@@ -491,16 +449,12 @@ where
|
|||||||
request_id: u64,
|
request_id: u64,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
let trace_id = *context.trace_id();
|
let trace_id = *context.trace_id();
|
||||||
let cancel = ClientMessage {
|
let cancel = ClientMessage::Cancel {
|
||||||
trace_context: context.trace_context,
|
trace_context: context.trace_context,
|
||||||
message: ClientMessageKind::Cancel { request_id },
|
request_id,
|
||||||
};
|
};
|
||||||
self.as_mut().transport().start_send(cancel)?;
|
self.as_mut().transport().start_send(cancel)?;
|
||||||
trace!(
|
trace!("[{}] Cancel message sent.", trace_id);
|
||||||
"[{}/{}] Cancel message sent.",
|
|
||||||
trace_id,
|
|
||||||
self.as_mut().server_addr()
|
|
||||||
);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -513,18 +467,13 @@ where
|
|||||||
{
|
{
|
||||||
self.as_mut().in_flight_requests().compact(0.1);
|
self.as_mut().in_flight_requests().compact(0.1);
|
||||||
|
|
||||||
trace!(
|
trace!("[{}] Received response.", in_flight_data.ctx.trace_id());
|
||||||
"[{}/{}] Received response.",
|
|
||||||
in_flight_data.ctx.trace_id(),
|
|
||||||
self.as_mut().server_addr()
|
|
||||||
);
|
|
||||||
let _ = in_flight_data.response_completion.send(response);
|
let _ = in_flight_data.response_completion.send(response);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"[{}] No in-flight request found for request_id = {}.",
|
"No in-flight request found for request_id = {}.",
|
||||||
self.as_mut().server_addr(),
|
|
||||||
response.request_id
|
response.request_id
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -537,58 +486,29 @@ impl<Req, Resp, C> Future for RequestDispatch<Req, Resp, C>
|
|||||||
where
|
where
|
||||||
Req: marker::Send,
|
Req: marker::Send,
|
||||||
Resp: marker::Send,
|
Resp: marker::Send,
|
||||||
C: Transport<Item = Response<Resp>, SinkItem = ClientMessage<Req>>,
|
C: Transport<ClientMessage<Req>, Response<Resp>>,
|
||||||
{
|
{
|
||||||
type Output = io::Result<()>;
|
type Output = io::Result<()>;
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||||
trace!("[{}] RequestDispatch::poll", self.as_mut().server_addr());
|
|
||||||
loop {
|
loop {
|
||||||
match (self.pump_read(cx)?, self.pump_write(cx)?) {
|
match (self.pump_read(cx)?, self.pump_write(cx)?) {
|
||||||
(read, write @ Poll::Ready(None)) => {
|
(read, Poll::Ready(None)) => {
|
||||||
if self.as_mut().in_flight_requests().is_empty() {
|
if self.as_mut().in_flight_requests().is_empty() {
|
||||||
info!(
|
info!("Shutdown: write half closed, and no requests in flight.");
|
||||||
"[{}] Shutdown: write half closed, and no requests in flight.",
|
|
||||||
self.as_mut().server_addr()
|
|
||||||
);
|
|
||||||
return Poll::Ready(Ok(()));
|
return Poll::Ready(Ok(()));
|
||||||
}
|
}
|
||||||
let addr = *self.as_mut().server_addr();
|
|
||||||
info!(
|
info!(
|
||||||
"[{}] {} requests in flight.",
|
"Shutdown: write half closed, and {} requests in flight.",
|
||||||
addr,
|
|
||||||
self.as_mut().in_flight_requests().len()
|
self.as_mut().in_flight_requests().len()
|
||||||
);
|
);
|
||||||
match read {
|
match read {
|
||||||
Poll::Ready(Some(())) => continue,
|
Poll::Ready(Some(())) => continue,
|
||||||
_ => {
|
_ => return Poll::Pending,
|
||||||
trace!(
|
|
||||||
"[{}] read: {:?}, write: {:?}, (not ready)",
|
|
||||||
self.as_mut().server_addr(),
|
|
||||||
read,
|
|
||||||
write,
|
|
||||||
);
|
|
||||||
return Poll::Pending;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(read @ Poll::Ready(Some(())), write) | (read, write @ Poll::Ready(Some(()))) => {
|
(Poll::Ready(Some(())), _) | (_, Poll::Ready(Some(()))) => {}
|
||||||
trace!(
|
_ => return Poll::Pending,
|
||||||
"[{}] read: {:?}, write: {:?}",
|
|
||||||
self.as_mut().server_addr(),
|
|
||||||
read,
|
|
||||||
write,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
(read, write) => {
|
|
||||||
trace!(
|
|
||||||
"[{}] read: {:?}, write: {:?} (not ready)",
|
|
||||||
self.as_mut().server_addr(),
|
|
||||||
read,
|
|
||||||
write,
|
|
||||||
);
|
|
||||||
return Poll::Pending;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -848,14 +768,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use futures_test::task::noop_waker_ref;
|
use futures_test::task::noop_waker_ref;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{
|
use std::{marker, pin::Pin, sync::atomic::AtomicU64, sync::Arc, time::Instant};
|
||||||
marker,
|
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
|
||||||
pin::Pin,
|
|
||||||
sync::atomic::AtomicU64,
|
|
||||||
sync::Arc,
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dispatch_response_cancels_on_timeout() {
|
fn dispatch_response_cancels_on_timeout() {
|
||||||
@@ -869,7 +782,6 @@ mod tests {
|
|||||||
request_id: 3,
|
request_id: 3,
|
||||||
cancellation,
|
cancellation,
|
||||||
ctx: context::current(),
|
ctx: context::current(),
|
||||||
server_addr: SocketAddr::from(([0, 0, 0, 0], 9999)),
|
|
||||||
};
|
};
|
||||||
{
|
{
|
||||||
pin_utils::pin_mut!(resp);
|
pin_utils::pin_mut!(resp);
|
||||||
@@ -994,7 +906,6 @@ mod tests {
|
|||||||
canceled_requests: CanceledRequests(canceled_requests).fuse(),
|
canceled_requests: CanceledRequests(canceled_requests).fuse(),
|
||||||
in_flight_requests: FnvHashMap::default(),
|
in_flight_requests: FnvHashMap::default(),
|
||||||
config: Config::default(),
|
config: Config::default(),
|
||||||
server_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let cancellation = RequestCancellation(cancel_tx);
|
let cancellation = RequestCancellation(cancel_tx);
|
||||||
@@ -1002,7 +913,6 @@ mod tests {
|
|||||||
to_dispatch,
|
to_dispatch,
|
||||||
cancellation,
|
cancellation,
|
||||||
next_request_id: Arc::new(AtomicU64::new(0)),
|
next_request_id: Arc::new(AtomicU64::new(0)),
|
||||||
server_addr: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
(dispatch, channel, server_channel)
|
(dispatch, channel, server_channel)
|
||||||
|
|||||||
@@ -8,11 +8,7 @@
|
|||||||
|
|
||||||
use crate::{context, ClientMessage, Response, Transport};
|
use crate::{context, ClientMessage, Response, Transport};
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use log::warn;
|
use std::io;
|
||||||
use std::{
|
|
||||||
io,
|
|
||||||
net::{Ipv4Addr, SocketAddr},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Provides a [`Client`] backed by a transport.
|
/// Provides a [`Client`] backed by a transport.
|
||||||
pub mod channel;
|
pub mod channel;
|
||||||
@@ -137,15 +133,7 @@ pub async fn new<Req, Resp, T>(config: Config, transport: T) -> io::Result<Chann
|
|||||||
where
|
where
|
||||||
Req: Send + 'static,
|
Req: Send + 'static,
|
||||||
Resp: Send + 'static,
|
Resp: Send + 'static,
|
||||||
T: Transport<Item = Response<Resp>, SinkItem = ClientMessage<Req>> + Send + 'static,
|
T: Transport<ClientMessage<Req>, Response<Resp>> + Send + 'static,
|
||||||
{
|
{
|
||||||
let server_addr = transport.peer_addr().unwrap_or_else(|e| {
|
Ok(channel::spawn(config, transport).await?)
|
||||||
warn!(
|
|
||||||
"Setting peer to unspecified because peer could not be determined: {}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0)
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(channel::spawn(config, transport, server_addr).await?)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,20 @@ use trace::{self, TraceId};
|
|||||||
/// The context should not be stored directly in a server implementation, because the context will
|
/// The context should not be stored directly in a server implementation, because the context will
|
||||||
/// be different for each request in scope.
|
/// be different for each request in scope.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
/// When the client expects the request to be complete by. The server should cancel the request
|
/// When the client expects the request to be complete by. The server should cancel the request
|
||||||
/// if it is not complete by this time.
|
/// if it is not complete by this time.
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde1",
|
||||||
|
serde(serialize_with = "crate::util::serde::serialize_epoch_secs")
|
||||||
|
)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde1",
|
||||||
|
serde(deserialize_with = "crate::util::serde::deserialize_epoch_secs")
|
||||||
|
)]
|
||||||
|
#[cfg_attr(feature = "serde1", serde(default = "ten_seconds_from_now"))]
|
||||||
pub deadline: SystemTime,
|
pub deadline: SystemTime,
|
||||||
/// Uniquely identifies requests originating from the same source.
|
/// Uniquely identifies requests originating from the same source.
|
||||||
/// When a service handles a request by making requests itself, those requests should
|
/// When a service handles a request by making requests itself, those requests should
|
||||||
@@ -28,6 +38,11 @@ pub struct Context {
|
|||||||
pub trace_context: trace::Context,
|
pub trace_context: trace::Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde1")]
|
||||||
|
fn ten_seconds_from_now() -> SystemTime {
|
||||||
|
return SystemTime::now() + Duration::from_secs(10);
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the context for the current request, or a default Context if no request is active.
|
/// 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.
|
// TODO: populate Context with request-scoped data, with default fallbacks.
|
||||||
pub fn current() -> Context {
|
pub fn current() -> Context {
|
||||||
|
|||||||
@@ -5,12 +5,14 @@
|
|||||||
// https://opensource.org/licenses/MIT.
|
// https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
#![feature(
|
#![feature(
|
||||||
|
weak_counts,
|
||||||
non_exhaustive,
|
non_exhaustive,
|
||||||
integer_atomics,
|
integer_atomics,
|
||||||
try_trait,
|
try_trait,
|
||||||
arbitrary_self_types,
|
arbitrary_self_types,
|
||||||
async_await,
|
async_await,
|
||||||
async_closure
|
trait_alias,
|
||||||
|
async_closure,
|
||||||
)]
|
)]
|
||||||
#![deny(missing_docs, missing_debug_implementations)]
|
#![deny(missing_docs, missing_debug_implementations)]
|
||||||
|
|
||||||
@@ -44,25 +46,14 @@ use futures::{
|
|||||||
task::{Poll, Spawn, SpawnError, SpawnExt},
|
task::{Poll, Spawn, SpawnError, SpawnExt},
|
||||||
Future,
|
Future,
|
||||||
};
|
};
|
||||||
use std::{cell::RefCell, io, sync::Once, time::SystemTime};
|
use once_cell::sync::OnceCell;
|
||||||
|
use std::{cell::RefCell, io, time::SystemTime};
|
||||||
|
|
||||||
/// A message from a client to a server.
|
/// A message from a client to a server.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct ClientMessage<T> {
|
pub enum 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
|
/// 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
|
/// service-provided request handler. The handler completes with a [`response`](Response), which
|
||||||
/// the server sends back to the client.
|
/// the server sends back to the client.
|
||||||
@@ -75,35 +66,30 @@ pub enum ClientMessageKind<T> {
|
|||||||
/// not be canceled, because the framework layer does not
|
/// not be canceled, because the framework layer does not
|
||||||
/// know about them.
|
/// know about them.
|
||||||
Cancel {
|
Cancel {
|
||||||
|
/// The trace context associates the message with a specific chain of causally-related actions,
|
||||||
|
/// possibly orchestrated across many distributed systems.
|
||||||
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
|
trace_context: trace::Context,
|
||||||
/// The ID of the request to cancel.
|
/// The ID of the request to cancel.
|
||||||
request_id: u64,
|
request_id: u64,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A request from a client to a server.
|
/// A request from a client to a server.
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Request<T> {
|
pub struct Request<T> {
|
||||||
|
/// Trace context, deadline, and other cross-cutting concerns.
|
||||||
|
pub context: context::Context,
|
||||||
/// Uniquely identifies the request across all requests sent over a single channel.
|
/// Uniquely identifies the request across all requests sent over a single channel.
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
/// The request body.
|
/// The request body.
|
||||||
pub message: T,
|
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.
|
/// A response from a server to a client.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Response<T> {
|
pub struct Response<T> {
|
||||||
@@ -114,7 +100,7 @@ pub struct Response<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An error response from a server to a client.
|
/// An error response from a server to a client.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct ServerError {
|
pub struct ServerError {
|
||||||
@@ -141,23 +127,17 @@ impl From<ServerError> for io::Error {
|
|||||||
impl<T> Request<T> {
|
impl<T> Request<T> {
|
||||||
/// Returns the deadline for this request.
|
/// Returns the deadline for this request.
|
||||||
pub fn deadline(&self) -> &SystemTime {
|
pub fn deadline(&self) -> &SystemTime {
|
||||||
&self.deadline
|
&self.context.deadline
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type PollIo<T> = Poll<Option<io::Result<T>>>;
|
pub(crate) type PollIo<T> = Poll<Option<io::Result<T>>>;
|
||||||
|
|
||||||
static INIT: Once = Once::new();
|
static SEED_SPAWN: OnceCell<Box<dyn CloneSpawn + Send + Sync>> = OnceCell::new();
|
||||||
static mut SEED_SPAWN: Option<Box<dyn CloneSpawn>> = None;
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static SPAWN: RefCell<Box<dyn CloneSpawn>> = {
|
static SPAWN: RefCell<Box<dyn CloneSpawn>> =
|
||||||
unsafe {
|
RefCell::new(SEED_SPAWN.get().expect("init() must be called.").box_clone());
|
||||||
// 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.as_ref().expect("init() must be called.").box_clone())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes the RPC library with a mechanism to spawn futures on the user's runtime.
|
/// Initializes the RPC library with a mechanism to spawn futures on the user's runtime.
|
||||||
@@ -165,12 +145,8 @@ thread_local! {
|
|||||||
///
|
///
|
||||||
/// Init only has an effect the first time it is called. If called previously, successive calls to
|
/// Init only has an effect the first time it is called. If called previously, successive calls to
|
||||||
/// init are noops.
|
/// init are noops.
|
||||||
pub fn init(spawn: impl Spawn + Clone + 'static) {
|
pub fn init(spawn: impl Spawn + Clone + Send + Sync + 'static) {
|
||||||
unsafe {
|
let _ = SEED_SPAWN.set(Box::new(spawn));
|
||||||
INIT.call_once(|| {
|
|
||||||
SEED_SPAWN = Some(Box::new(spawn));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn spawn(future: impl Future<Output = ()> + Send + 'static) -> Result<(), SpawnError> {
|
pub(crate) fn spawn(future: impl Future<Output = ()> + Send + 'static) -> Result<(), SpawnError> {
|
||||||
|
|||||||
@@ -5,259 +5,331 @@
|
|||||||
// https://opensource.org/licenses/MIT.
|
// https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
server::{Channel, Config},
|
server::{self, Channel},
|
||||||
util::Compact,
|
util::Compact,
|
||||||
ClientMessage, PollIo, Response, Transport,
|
Response,
|
||||||
};
|
};
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::mpsc,
|
channel::mpsc,
|
||||||
|
future::AbortRegistration,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
ready,
|
ready,
|
||||||
stream::Fuse,
|
stream::Fuse,
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, info, trace};
|
||||||
use pin_utils::unsafe_pinned;
|
use pin_utils::{unsafe_pinned, unsafe_unpinned};
|
||||||
|
use std::sync::{Arc, Weak};
|
||||||
use std::{
|
use std::{
|
||||||
collections::hash_map::Entry,
|
collections::hash_map::Entry, convert::TryInto, fmt, hash::Hash, io, marker::Unpin, ops::Try,
|
||||||
io,
|
|
||||||
marker::PhantomData,
|
|
||||||
net::{IpAddr, SocketAddr},
|
|
||||||
ops::Try,
|
|
||||||
option::NoneError,
|
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Drops connections under configurable conditions:
|
/// A single-threaded filter that drops channels based on per-key limits.
|
||||||
///
|
|
||||||
/// 1. If the max number of connections is reached.
|
|
||||||
/// 2. If the max number of connections for a single IP is reached.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ConnectionFilter<S, Req, Resp> {
|
pub struct ChannelFilter<S, K, F>
|
||||||
|
where
|
||||||
|
K: Eq + Hash,
|
||||||
|
{
|
||||||
listener: Fuse<S>,
|
listener: Fuse<S>,
|
||||||
closed_connections: mpsc::UnboundedSender<SocketAddr>,
|
channels_per_key: u32,
|
||||||
closed_connections_rx: mpsc::UnboundedReceiver<SocketAddr>,
|
dropped_keys: mpsc::UnboundedReceiver<K>,
|
||||||
config: Config,
|
dropped_keys_tx: mpsc::UnboundedSender<K>,
|
||||||
connections_per_ip: FnvHashMap<IpAddr, usize>,
|
key_counts: FnvHashMap<K, Weak<Tracker<K>>>,
|
||||||
open_connections: usize,
|
keymaker: F,
|
||||||
ghost: PhantomData<(Req, Resp)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NewConnection<Req, Resp, C> {
|
/// A channel that is tracked by a ChannelFilter.
|
||||||
Filtered,
|
#[derive(Debug)]
|
||||||
Accepted(Channel<Req, Resp, C>),
|
pub struct TrackedChannel<C, K> {
|
||||||
|
inner: C,
|
||||||
|
tracker: Arc<Tracker<K>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Req, Resp, C> Try for NewConnection<Req, Resp, C> {
|
impl<C, K> TrackedChannel<C, K> {
|
||||||
type Ok = Channel<Req, Resp, C>;
|
unsafe_pinned!(inner: C);
|
||||||
type Error = NoneError;
|
}
|
||||||
|
|
||||||
fn into_result(self) -> Result<Channel<Req, Resp, C>, NoneError> {
|
#[derive(Debug)]
|
||||||
|
struct Tracker<K> {
|
||||||
|
key: Option<K>,
|
||||||
|
dropped_keys: mpsc::UnboundedSender<K>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K> Drop for Tracker<K> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Don't care if the listener is dropped.
|
||||||
|
let _ = self.dropped_keys.unbounded_send(self.key.take().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A running handler serving all requests for a single client.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TrackedHandler<K, Fut> {
|
||||||
|
inner: Fut,
|
||||||
|
tracker: Tracker<K>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, Fut> TrackedHandler<K, Fut>
|
||||||
|
where
|
||||||
|
Fut: Future,
|
||||||
|
{
|
||||||
|
unsafe_pinned!(inner: Fut);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, Fut> Future for TrackedHandler<K, Fut>
|
||||||
|
where
|
||||||
|
Fut: Future,
|
||||||
|
{
|
||||||
|
type Output = Fut::Output;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
self.inner().poll(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, K> Stream for TrackedChannel<C, K>
|
||||||
|
where
|
||||||
|
C: Channel,
|
||||||
|
{
|
||||||
|
type Item = <C as Stream>::Item;
|
||||||
|
|
||||||
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||||
|
self.channel().poll_next(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, K> Sink<Response<C::Resp>> for TrackedChannel<C, K>
|
||||||
|
where
|
||||||
|
C: Channel,
|
||||||
|
{
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.channel().poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_send(self: Pin<&mut Self>, item: Response<C::Resp>) -> Result<(), Self::Error> {
|
||||||
|
self.channel().start_send(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.channel().poll_flush(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.channel().poll_close(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, K> AsRef<C> for TrackedChannel<C, K> {
|
||||||
|
fn as_ref(&self) -> &C {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, K> Channel for TrackedChannel<C, K>
|
||||||
|
where
|
||||||
|
C: Channel,
|
||||||
|
{
|
||||||
|
type Req = C::Req;
|
||||||
|
type Resp = C::Resp;
|
||||||
|
|
||||||
|
fn config(&self) -> &server::Config {
|
||||||
|
self.inner.config()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_flight_requests(self: Pin<&mut Self>) -> usize {
|
||||||
|
self.inner().in_flight_requests()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_request(self: Pin<&mut Self>, request_id: u64) -> AbortRegistration {
|
||||||
|
self.inner().start_request(request_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, K> TrackedChannel<C, K> {
|
||||||
|
/// Returns the inner channel.
|
||||||
|
pub fn get_ref(&self) -> &C {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the pinned inner channel.
|
||||||
|
fn channel<'a>(self: Pin<&'a mut Self>) -> Pin<&'a mut C> {
|
||||||
|
self.inner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NewChannel<C, K> {
|
||||||
|
Accepted(TrackedChannel<C, K>),
|
||||||
|
Filtered(K),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, K> Try for NewChannel<C, K> {
|
||||||
|
type Ok = TrackedChannel<C, K>;
|
||||||
|
type Error = K;
|
||||||
|
|
||||||
|
fn into_result(self) -> Result<TrackedChannel<C, K>, K> {
|
||||||
match self {
|
match self {
|
||||||
NewConnection::Filtered => Err(NoneError),
|
NewChannel::Accepted(channel) => Ok(channel),
|
||||||
NewConnection::Accepted(channel) => Ok(channel),
|
NewChannel::Filtered(k) => Err(k),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_error(_: NoneError) -> Self {
|
fn from_error(k: K) -> Self {
|
||||||
NewConnection::Filtered
|
NewChannel::Filtered(k)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_ok(channel: Channel<Req, Resp, C>) -> Self {
|
fn from_ok(channel: TrackedChannel<C, K>) -> Self {
|
||||||
NewConnection::Accepted(channel)
|
NewChannel::Accepted(channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, Req, Resp> ConnectionFilter<S, Req, Resp> {
|
impl<S, K, F> ChannelFilter<S, K, F>
|
||||||
unsafe_pinned!(open_connections: usize);
|
where
|
||||||
unsafe_pinned!(config: Config);
|
K: fmt::Display + Eq + Hash + Clone,
|
||||||
unsafe_pinned!(connections_per_ip: FnvHashMap<IpAddr, usize>);
|
{
|
||||||
unsafe_pinned!(closed_connections_rx: mpsc::UnboundedReceiver<SocketAddr>);
|
|
||||||
unsafe_pinned!(listener: Fuse<S>);
|
unsafe_pinned!(listener: Fuse<S>);
|
||||||
|
unsafe_pinned!(dropped_keys: mpsc::UnboundedReceiver<K>);
|
||||||
|
unsafe_pinned!(dropped_keys_tx: mpsc::UnboundedSender<K>);
|
||||||
|
unsafe_unpinned!(key_counts: FnvHashMap<K, Weak<Tracker<K>>>);
|
||||||
|
unsafe_unpinned!(channels_per_key: u32);
|
||||||
|
unsafe_unpinned!(keymaker: F);
|
||||||
|
}
|
||||||
|
|
||||||
/// Sheds new connections to stay under configured limits.
|
impl<S, K, F> ChannelFilter<S, K, F>
|
||||||
pub fn filter<C>(listener: S, config: Config) -> Self
|
where
|
||||||
where
|
K: Eq + Hash,
|
||||||
S: Stream<Item = Result<C, io::Error>>,
|
S: Stream,
|
||||||
C: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
{
|
||||||
{
|
/// Sheds new channels to stay under configured limits.
|
||||||
let (closed_connections, closed_connections_rx) = mpsc::unbounded();
|
pub(crate) fn new(listener: S, channels_per_key: u32, keymaker: F) -> Self {
|
||||||
|
let (dropped_keys_tx, dropped_keys) = mpsc::unbounded();
|
||||||
ConnectionFilter {
|
ChannelFilter {
|
||||||
listener: listener.fuse(),
|
listener: listener.fuse(),
|
||||||
closed_connections,
|
channels_per_key,
|
||||||
closed_connections_rx,
|
dropped_keys,
|
||||||
config,
|
dropped_keys_tx,
|
||||||
connections_per_ip: FnvHashMap::default(),
|
key_counts: FnvHashMap::default(),
|
||||||
open_connections: 0,
|
keymaker,
|
||||||
ghost: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_new_connection<C>(self: &mut Pin<&mut Self>, stream: C) -> NewConnection<Req, Resp, C>
|
impl<S, C, K, F> ChannelFilter<S, K, F>
|
||||||
where
|
where
|
||||||
C: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
S: Stream<Item = C>,
|
||||||
{
|
C: Channel,
|
||||||
let peer = match stream.peer_addr() {
|
K: fmt::Display + Eq + Hash + Clone + Unpin,
|
||||||
Ok(peer) => peer,
|
F: Fn(&C) -> K,
|
||||||
Err(e) => {
|
{
|
||||||
warn!("Could not get peer_addr of new connection: {}", e);
|
fn handle_new_channel(self: &mut Pin<&mut Self>, stream: C) -> NewChannel<C, K> {
|
||||||
return NewConnection::Filtered;
|
let key = self.as_mut().keymaker()(&stream);
|
||||||
}
|
let tracker = self.increment_channels_for_key(key.clone())?;
|
||||||
};
|
let max = self.as_mut().channels_per_key();
|
||||||
|
|
||||||
let open_connections = *self.as_mut().open_connections();
|
|
||||||
if open_connections >= self.as_mut().config().max_connections {
|
|
||||||
warn!(
|
|
||||||
"[{}] Shedding connection because the maximum open connections \
|
|
||||||
limit is reached ({}/{}).",
|
|
||||||
peer,
|
|
||||||
open_connections,
|
|
||||||
self.as_mut().config().max_connections
|
|
||||||
);
|
|
||||||
return NewConnection::Filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = self.config.clone();
|
|
||||||
let open_connections_for_ip = self.increment_connections_for_ip(&peer)?;
|
|
||||||
*self.as_mut().open_connections() += 1;
|
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"[{}] Opening channel ({}/{} connections for IP, {} total).",
|
"[{}] Opening channel ({}/{}) channels for key.",
|
||||||
peer,
|
key,
|
||||||
open_connections_for_ip,
|
Arc::strong_count(&tracker),
|
||||||
config.max_connections_per_ip,
|
max
|
||||||
self.as_mut().open_connections(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
NewConnection::Accepted(Channel {
|
NewChannel::Accepted(TrackedChannel {
|
||||||
client_addr: peer,
|
tracker,
|
||||||
closed_connections: self.closed_connections.clone(),
|
inner: stream,
|
||||||
transport: stream.fuse(),
|
|
||||||
config,
|
|
||||||
ghost: PhantomData,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_closed_connection(self: &mut Pin<&mut Self>, addr: &SocketAddr) {
|
fn increment_channels_for_key(self: &mut Pin<&mut Self>, key: K) -> Result<Arc<Tracker<K>>, K> {
|
||||||
*self.as_mut().open_connections() -= 1;
|
let channels_per_key = self.channels_per_key;
|
||||||
debug!(
|
let dropped_keys = self.dropped_keys_tx.clone();
|
||||||
"[{}] Closing channel. {} open connections remaining.",
|
let key_counts = &mut self.as_mut().key_counts();
|
||||||
addr, self.open_connections
|
match key_counts.entry(key.clone()) {
|
||||||
);
|
Entry::Vacant(vacant) => {
|
||||||
self.decrement_connections_for_ip(&addr);
|
let tracker = Arc::new(Tracker {
|
||||||
self.as_mut().connections_per_ip().compact(0.1);
|
key: Some(key),
|
||||||
}
|
dropped_keys,
|
||||||
|
});
|
||||||
|
|
||||||
fn increment_connections_for_ip(self: &mut Pin<&mut Self>, peer: &SocketAddr) -> Option<usize> {
|
vacant.insert(Arc::downgrade(&tracker));
|
||||||
let max_connections_per_ip = self.as_mut().config().max_connections_per_ip;
|
Ok(tracker)
|
||||||
let mut occupied;
|
}
|
||||||
let mut connections_per_ip = self.as_mut().connections_per_ip();
|
Entry::Occupied(mut o) => {
|
||||||
let occupied = match connections_per_ip.entry(peer.ip()) {
|
let count = o.get().strong_count();
|
||||||
Entry::Vacant(vacant) => vacant.insert(0),
|
if count >= channels_per_key.try_into().unwrap() {
|
||||||
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!(
|
info!(
|
||||||
"[{}] Opened max connections from IP ({}/{}).",
|
"[{}] Opened max channels from key ({}/{}).",
|
||||||
peer,
|
key, count, channels_per_key
|
||||||
o.get(),
|
|
||||||
max_connections_per_ip
|
|
||||||
);
|
);
|
||||||
return None;
|
Err(key)
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
*occupied += 1;
|
|
||||||
Some(*occupied)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decrement_connections_for_ip(self: &mut Pin<&mut Self>, addr: &SocketAddr) {
|
|
||||||
let should_compact = match self.as_mut().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 {
|
} else {
|
||||||
false
|
Ok(o.get().upgrade().unwrap_or_else(|| {
|
||||||
|
let tracker = Arc::new(Tracker {
|
||||||
|
key: Some(key),
|
||||||
|
dropped_keys,
|
||||||
|
});
|
||||||
|
|
||||||
|
*o.get_mut() = Arc::downgrade(&tracker);
|
||||||
|
tracker
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
if should_compact {
|
|
||||||
self.as_mut().connections_per_ip().compact(0.1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_listener<C>(
|
fn poll_listener(
|
||||||
mut self: Pin<&mut Self>,
|
mut self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> PollIo<NewConnection<Req, Resp, C>>
|
) -> Poll<Option<NewChannel<C, K>>> {
|
||||||
where
|
match ready!(self.as_mut().listener().poll_next_unpin(cx)) {
|
||||||
S: Stream<Item = Result<C, io::Error>>,
|
Some(codec) => Poll::Ready(Some(self.handle_new_channel(codec))),
|
||||||
C: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
|
||||||
{
|
|
||||||
match ready!(self.as_mut().listener().poll_next_unpin(cx)?) {
|
|
||||||
Some(codec) => Poll::Ready(Some(Ok(self.handle_new_connection(codec)))),
|
|
||||||
None => Poll::Ready(None),
|
None => Poll::Ready(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_closed_connections(
|
fn poll_closed_channels(self: &mut Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||||
self: &mut Pin<&mut Self>,
|
match ready!(self.as_mut().dropped_keys().poll_next_unpin(cx)) {
|
||||||
cx: &mut Context<'_>,
|
Some(key) => {
|
||||||
) -> Poll<io::Result<()>> {
|
debug!("All channels dropped for key [{}]", key);
|
||||||
match ready!(self.as_mut().closed_connections_rx().poll_next_unpin(cx)) {
|
self.as_mut().key_counts().remove(&key);
|
||||||
Some(addr) => {
|
self.as_mut().key_counts().compact(0.1);
|
||||||
self.handle_closed_connection(&addr);
|
Poll::Ready(())
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
}
|
||||||
None => unreachable!("Holding a copy of closed_connections and didn't close it."),
|
None => unreachable!("Holding a copy of closed_channels and didn't close it."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, Req, Resp, T> Stream for ConnectionFilter<S, Req, Resp>
|
impl<S, C, K, F> Stream for ChannelFilter<S, K, F>
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<T, io::Error>>,
|
S: Stream<Item = C>,
|
||||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
C: Channel,
|
||||||
|
K: fmt::Display + Eq + Hash + Clone + Unpin,
|
||||||
|
F: Fn(&C) -> K,
|
||||||
{
|
{
|
||||||
type Item = io::Result<Channel<Req, Resp, T>>;
|
type Item = TrackedChannel<C, K>;
|
||||||
|
|
||||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> PollIo<Channel<Req, Resp, T>> {
|
fn poll_next(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<TrackedChannel<C, K>>> {
|
||||||
loop {
|
loop {
|
||||||
match (
|
match (
|
||||||
self.as_mut().poll_listener(cx)?,
|
self.as_mut().poll_listener(cx),
|
||||||
self.poll_closed_connections(cx)?,
|
self.poll_closed_channels(cx),
|
||||||
) {
|
) {
|
||||||
(Poll::Ready(Some(NewConnection::Accepted(channel))), _) => {
|
(Poll::Ready(Some(NewChannel::Accepted(channel))), _) => {
|
||||||
return Poll::Ready(Some(Ok(channel)));
|
return Poll::Ready(Some(channel));
|
||||||
}
|
}
|
||||||
(Poll::Ready(Some(NewConnection::Filtered)), _) | (_, Poll::Ready(())) => {
|
(Poll::Ready(Some(NewChannel::Filtered(_))), _) => {
|
||||||
trace!(
|
|
||||||
"Filtered a connection; {} open.",
|
|
||||||
self.as_mut().open_connections()
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
(_, Poll::Ready(())) => continue,
|
||||||
(Poll::Pending, Poll::Pending) => return Poll::Pending,
|
(Poll::Pending, Poll::Pending) => return Poll::Pending,
|
||||||
(Poll::Ready(None), Poll::Pending) => {
|
(Poll::Ready(None), Poll::Pending) => {
|
||||||
if *self.as_mut().open_connections() > 0 {
|
trace!("Shutting down listener.");
|
||||||
trace!(
|
|
||||||
"Listener closed; {} open connections.",
|
|
||||||
self.as_mut().open_connections()
|
|
||||||
);
|
|
||||||
return Poll::Pending;
|
|
||||||
}
|
|
||||||
trace!("Shutting down listener: all connections closed, and no more coming.");
|
|
||||||
return Poll::Ready(None);
|
return Poll::Ready(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
//! Provides a server that concurrently handles many connections sending multiplexed requests.
|
//! Provides a server that concurrently handles many connections sending multiplexed requests.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context, util::deadline_compat, util::AsDuration, util::Compact, ClientMessage,
|
context, util::deadline_compat, util::AsDuration, util::Compact, ClientMessage, PollIo,
|
||||||
ClientMessageKind, PollIo, Request, Response, ServerError, Transport,
|
Request, Response, ServerError, Transport,
|
||||||
};
|
};
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::mpsc,
|
channel::mpsc,
|
||||||
future::{abortable, AbortHandle},
|
future::{AbortHandle, AbortRegistration, Abortable},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
ready,
|
ready,
|
||||||
stream::Fuse,
|
stream::Fuse,
|
||||||
@@ -24,9 +24,10 @@ use log::{debug, error, info, trace, warn};
|
|||||||
use pin_utils::{unsafe_pinned, unsafe_unpinned};
|
use pin_utils::{unsafe_pinned, unsafe_unpinned};
|
||||||
use std::{
|
use std::{
|
||||||
error::Error as StdError,
|
error::Error as StdError,
|
||||||
|
fmt,
|
||||||
|
hash::Hash,
|
||||||
io,
|
io,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
net::SocketAddr,
|
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
time::{Instant, SystemTime},
|
time::{Instant, SystemTime},
|
||||||
};
|
};
|
||||||
@@ -34,6 +35,14 @@ use tokio_timer::timeout;
|
|||||||
use trace::{self, TraceId};
|
use trace::{self, TraceId};
|
||||||
|
|
||||||
mod filter;
|
mod filter;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod testing;
|
||||||
|
mod throttle;
|
||||||
|
|
||||||
|
pub use self::{
|
||||||
|
filter::ChannelFilter,
|
||||||
|
throttle::{Throttler, ThrottlerStream},
|
||||||
|
};
|
||||||
|
|
||||||
/// Manages clients, serving multiplexed requests over each connection.
|
/// Manages clients, serving multiplexed requests over each connection.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -52,17 +61,6 @@ impl<Req, Resp> Default for Server<Req, Resp> {
|
|||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Config {
|
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.
|
/// 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
|
/// `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.
|
/// response tasks use to send responses to the client handler task.
|
||||||
@@ -72,14 +70,21 @@ pub struct Config {
|
|||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Config {
|
Config {
|
||||||
max_connections: 1_000_000,
|
|
||||||
max_connections_per_ip: 1_000,
|
|
||||||
max_in_flight_requests_per_connection: 1_000,
|
|
||||||
pending_response_buffer: 100,
|
pending_response_buffer: 100,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Returns a channel backed by `transport` and configured with `self`.
|
||||||
|
pub fn channel<Req, Resp, T>(self, transport: T) -> BaseChannel<Req, Resp, T>
|
||||||
|
where
|
||||||
|
T: Transport<Response<Resp>, ClientMessage<Req>> + Send,
|
||||||
|
{
|
||||||
|
BaseChannel::new(self, transport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a new server with configuration specified `config`.
|
/// Returns a new server with configuration specified `config`.
|
||||||
pub fn new<Req, Resp>(config: Config) -> Server<Req, Resp> {
|
pub fn new<Req, Resp>(config: Config) -> Server<Req, Resp> {
|
||||||
Server {
|
Server {
|
||||||
@@ -94,18 +99,15 @@ impl<Req, Resp> Server<Req, Resp> {
|
|||||||
&self.config
|
&self.config
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a stream of the incoming connections to the server.
|
/// Returns a stream of server channels.
|
||||||
pub fn incoming<S, T>(
|
pub fn incoming<S, T>(self, listener: S) -> impl Stream<Item = BaseChannel<Req, Resp, T>>
|
||||||
self,
|
|
||||||
listener: S,
|
|
||||||
) -> impl Stream<Item = io::Result<Channel<Req, Resp, T>>>
|
|
||||||
where
|
where
|
||||||
Req: Send,
|
Req: Send,
|
||||||
Resp: Send,
|
Resp: Send,
|
||||||
S: Stream<Item = io::Result<T>>,
|
S: Stream<Item = T>,
|
||||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
T: Transport<Response<Resp>, ClientMessage<Req>> + Send,
|
||||||
{
|
{
|
||||||
self::filter::ConnectionFilter::filter(listener, self.config.clone())
|
listener.map(move |t| BaseChannel::new(self.config.clone(), t))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,31 +123,21 @@ impl<S, F> Running<S, F> {
|
|||||||
unsafe_unpinned!(request_handler: F);
|
unsafe_unpinned!(request_handler: F);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, T, Req, Resp, F, Fut> Future for Running<S, F>
|
impl<S, C, F, Fut> Future for Running<S, F>
|
||||||
where
|
where
|
||||||
S: Sized + Stream<Item = io::Result<Channel<Req, Resp, T>>>,
|
S: Sized + Stream<Item = C>,
|
||||||
Req: Send + 'static,
|
C: Channel + Send + 'static,
|
||||||
Resp: Send + 'static,
|
F: FnOnce(context::Context, C::Req) -> Fut + Send + 'static + Clone,
|
||||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send + 'static,
|
Fut: Future<Output = io::Result<C::Resp>> + Send + 'static,
|
||||||
F: FnOnce(context::Context, Req) -> Fut + Send + 'static + Clone,
|
|
||||||
Fut: Future<Output = io::Result<Resp>> + Send + 'static,
|
|
||||||
{
|
{
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||||
while let Some(channel) = ready!(self.as_mut().incoming().poll_next(cx)) {
|
while let Some(channel) = ready!(self.as_mut().incoming().poll_next(cx)) {
|
||||||
match channel {
|
if let Err(e) =
|
||||||
Ok(channel) => {
|
crate::spawn(channel.respond_with(self.as_mut().request_handler().clone()))
|
||||||
let peer = channel.client_addr;
|
{
|
||||||
if let Err(e) =
|
warn!("Failed to spawn channel handler: {:?}", e);
|
||||||
crate::spawn(channel.respond_with(self.as_mut().request_handler().clone()))
|
|
||||||
{
|
|
||||||
warn!("[{}] Failed to spawn connection handler: {:?}", peer, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Incoming connection error: {}", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("Server shutting down.");
|
info!("Server shutting down.");
|
||||||
@@ -154,18 +146,30 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A utility trait enabling a stream to fluently chain a request handler.
|
/// A utility trait enabling a stream to fluently chain a request handler.
|
||||||
pub trait Handler<T, Req, Resp>
|
pub trait Handler<C>
|
||||||
where
|
where
|
||||||
Self: Sized + Stream<Item = io::Result<Channel<Req, Resp, T>>>,
|
Self: Sized + Stream<Item = C>,
|
||||||
Req: Send,
|
C: Channel,
|
||||||
Resp: Send,
|
|
||||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
|
||||||
{
|
{
|
||||||
|
/// Enforces channel per-key limits.
|
||||||
|
fn max_channels_per_key<K, KF>(self, n: u32, keymaker: KF) -> filter::ChannelFilter<Self, K, KF>
|
||||||
|
where
|
||||||
|
K: fmt::Display + Eq + Hash + Clone + Unpin,
|
||||||
|
KF: Fn(&C) -> K,
|
||||||
|
{
|
||||||
|
ChannelFilter::new(self, n, keymaker)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Caps the number of concurrent requests per channel.
|
||||||
|
fn max_concurrent_requests_per_channel(self, n: usize) -> ThrottlerStream<Self> {
|
||||||
|
ThrottlerStream::new(self, n)
|
||||||
|
}
|
||||||
|
|
||||||
/// Responds to all requests with `request_handler`.
|
/// Responds to all requests with `request_handler`.
|
||||||
fn respond_with<F, Fut>(self, request_handler: F) -> Running<Self, F>
|
fn respond_with<F, Fut>(self, request_handler: F) -> Running<Self, F>
|
||||||
where
|
where
|
||||||
F: FnOnce(context::Context, Req) -> Fut + Send + 'static + Clone,
|
F: FnOnce(context::Context, C::Req) -> Fut + Send + 'static + Clone,
|
||||||
Fut: Future<Output = io::Result<Resp>> + Send + 'static,
|
Fut: Future<Output = io::Result<C::Resp>> + Send + 'static,
|
||||||
{
|
{
|
||||||
Running {
|
Running {
|
||||||
incoming: self,
|
incoming: self,
|
||||||
@@ -174,191 +178,276 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, Req, Resp, S> Handler<T, Req, Resp> for S
|
impl<S, C> Handler<C> for S
|
||||||
where
|
where
|
||||||
S: Sized + Stream<Item = io::Result<Channel<Req, Resp, T>>>,
|
S: Sized + Stream<Item = C>,
|
||||||
Req: Send,
|
C: Channel,
|
||||||
Resp: Send,
|
|
||||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Responds to all requests with `request_handler`.
|
/// BaseChannel lifts a Transport to a Channel by tracking in-flight requests.
|
||||||
/// The server end of an open connection with a client.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Channel<Req, Resp, T> {
|
pub struct BaseChannel<Req, Resp, T> {
|
||||||
|
config: Config,
|
||||||
/// Writes responses to the wire and reads requests off the wire.
|
/// Writes responses to the wire and reads requests off the wire.
|
||||||
transport: Fuse<T>,
|
transport: Fuse<T>,
|
||||||
/// Signals the connection is closed when `Channel` is dropped.
|
/// Number of requests currently being responded to.
|
||||||
closed_connections: mpsc::UnboundedSender<SocketAddr>,
|
in_flight_requests: FnvHashMap<u64, AbortHandle>,
|
||||||
/// Channel limits to prevent unlimited resource usage.
|
|
||||||
config: Config,
|
|
||||||
/// The address of the server connected to.
|
|
||||||
client_addr: SocketAddr,
|
|
||||||
/// Types the request and response.
|
/// Types the request and response.
|
||||||
ghost: PhantomData<(Req, Resp)>,
|
ghost: PhantomData<(Req, Resp)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Req, Resp, T> Drop for Channel<Req, Resp, T> {
|
impl<Req, Resp, T> BaseChannel<Req, Resp, T> {
|
||||||
fn drop(&mut self) {
|
unsafe_unpinned!(in_flight_requests: FnvHashMap<u64, AbortHandle>);
|
||||||
trace!("[{}] Closing channel.", self.client_addr);
|
}
|
||||||
|
|
||||||
// Even in a bounded channel, each connection would have a guaranteed slot, so using
|
impl<Req, Resp, T> BaseChannel<Req, Resp, T>
|
||||||
// an unbounded sender is actually no different. And, the bound is on the maximum number
|
where
|
||||||
// of open connections.
|
T: Transport<Response<Resp>, ClientMessage<Req>> + Send,
|
||||||
if self
|
{
|
||||||
.closed_connections
|
/// Creates a new channel backed by `transport` and configured with `config`.
|
||||||
.unbounded_send(self.client_addr)
|
pub fn new(config: Config, transport: T) -> Self {
|
||||||
.is_err()
|
BaseChannel {
|
||||||
{
|
config,
|
||||||
warn!(
|
transport: transport.fuse(),
|
||||||
"[{}] Failed to send closed connection message.",
|
in_flight_requests: FnvHashMap::default(),
|
||||||
self.client_addr
|
ghost: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new channel backed by `transport` and configured with the defaults.
|
||||||
|
pub fn with_defaults(transport: T) -> Self {
|
||||||
|
Self::new(Config::default(), transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the inner transport.
|
||||||
|
pub fn get_ref(&self) -> &T {
|
||||||
|
self.transport.get_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the pinned inner transport.
|
||||||
|
pub fn transport<'a>(self: Pin<&'a mut Self>) -> Pin<&'a mut T> {
|
||||||
|
unsafe { self.map_unchecked_mut(|me| me.transport.get_mut()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel_request(mut self: 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.as_mut().in_flight_requests().remove(&request_id) {
|
||||||
|
self.as_mut().in_flight_requests().compact(0.1);
|
||||||
|
|
||||||
|
cancel_handle.abort();
|
||||||
|
let remaining = self.as_mut().in_flight_requests().len();
|
||||||
|
trace!(
|
||||||
|
"[{}] Request canceled. In-flight requests = {}",
|
||||||
|
trace_context.trace_id,
|
||||||
|
remaining,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
trace!(
|
||||||
|
"[{}] Received cancellation, but response handler \
|
||||||
|
is already complete.",
|
||||||
|
trace_context.trace_id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Req, Resp, T> Channel<Req, Resp, T> {
|
/// The server end of an open connection with a client, streaming in requests from, and sinking
|
||||||
unsafe_pinned!(transport: Fuse<T>);
|
/// responses to, the client.
|
||||||
}
|
///
|
||||||
|
/// Channels are free to somewhat rely on the assumption that all in-flight requests are eventually
|
||||||
impl<Req, Resp, T> Channel<Req, Resp, T>
|
/// either [cancelled](BaseChannel::cancel_request) or [responded to](Sink::start_send). Safety cannot
|
||||||
|
/// rely on this assumption, but it is best for `Channel` users to always account for all outstanding
|
||||||
|
/// requests.
|
||||||
|
pub trait Channel
|
||||||
where
|
where
|
||||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
Self: Transport<Response<<Self as Channel>::Resp>, Request<<Self as Channel>::Req>>,
|
||||||
Req: Send,
|
|
||||||
Resp: Send,
|
|
||||||
{
|
{
|
||||||
pub(crate) fn start_send(mut self: Pin<&mut Self>, response: Response<Resp>) -> io::Result<()> {
|
/// Type of request item.
|
||||||
self.as_mut().transport().start_send(response)
|
type Req: Send + 'static;
|
||||||
|
|
||||||
|
/// Type of response sink item.
|
||||||
|
type Resp: Send + 'static;
|
||||||
|
|
||||||
|
/// Configuration of the channel.
|
||||||
|
fn config(&self) -> &Config;
|
||||||
|
|
||||||
|
/// Returns the number of in-flight requests over this channel.
|
||||||
|
fn in_flight_requests(self: Pin<&mut Self>) -> usize;
|
||||||
|
|
||||||
|
/// Caps the number of concurrent requests.
|
||||||
|
fn max_concurrent_requests(self, n: usize) -> Throttler<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Throttler::new(self, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn poll_ready(
|
/// Tells the Channel that request with ID `request_id` is being handled.
|
||||||
mut self: Pin<&mut Self>,
|
/// The request will be tracked until a response with the same ID is sent
|
||||||
cx: &mut Context<'_>,
|
/// to the Channel.
|
||||||
) -> Poll<io::Result<()>> {
|
fn start_request(self: Pin<&mut Self>, request_id: u64) -> AbortRegistration;
|
||||||
self.as_mut().transport().poll_ready(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn poll_flush(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<io::Result<()>> {
|
|
||||||
self.as_mut().transport().poll_flush(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn poll_next(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> PollIo<ClientMessage<Req>> {
|
|
||||||
self.as_mut().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
|
/// Respond to requests coming over the channel with `f`. Returns a future that drives the
|
||||||
/// responses and resolves when the connection is closed.
|
/// responses and resolves when the connection is closed.
|
||||||
pub fn respond_with<F, Fut>(self, f: F) -> impl Future<Output = ()>
|
fn respond_with<F, Fut>(self, f: F) -> ResponseHandler<Self, F>
|
||||||
where
|
where
|
||||||
F: FnOnce(context::Context, Req) -> Fut + Send + 'static + Clone,
|
F: FnOnce(context::Context, Self::Req) -> Fut + Send + 'static + Clone,
|
||||||
Fut: Future<Output = io::Result<Resp>> + Send + 'static,
|
Fut: Future<Output = io::Result<Self::Resp>> + Send + 'static,
|
||||||
Req: 'static,
|
Self: Sized,
|
||||||
Resp: 'static,
|
|
||||||
{
|
{
|
||||||
let (responses_tx, responses) = mpsc::channel(self.config.pending_response_buffer);
|
let (responses_tx, responses) = mpsc::channel(self.config().pending_response_buffer);
|
||||||
let responses = responses.fuse();
|
let responses = responses.fuse();
|
||||||
let peer = self.client_addr;
|
|
||||||
|
|
||||||
ClientHandler {
|
ResponseHandler {
|
||||||
channel: self,
|
channel: self,
|
||||||
f,
|
f,
|
||||||
pending_responses: responses,
|
pending_responses: responses,
|
||||||
responses_tx,
|
responses_tx,
|
||||||
in_flight_requests: FnvHashMap::default(),
|
|
||||||
}
|
}
|
||||||
.unwrap_or_else(move |e| {
|
|
||||||
info!("[{}] ClientHandler errored out: {}", peer, e);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Req, Resp, T> Stream for BaseChannel<Req, Resp, T>
|
||||||
|
where
|
||||||
|
T: Transport<Response<Resp>, ClientMessage<Req>> + Send + 'static,
|
||||||
|
Req: Send + 'static,
|
||||||
|
Resp: Send + 'static,
|
||||||
|
{
|
||||||
|
type Item = io::Result<Request<Req>>;
|
||||||
|
|
||||||
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||||
|
loop {
|
||||||
|
match ready!(self.as_mut().transport().poll_next(cx)?) {
|
||||||
|
Some(message) => match message {
|
||||||
|
ClientMessage::Request(request) => {
|
||||||
|
return Poll::Ready(Some(Ok(request)));
|
||||||
|
}
|
||||||
|
ClientMessage::Cancel {
|
||||||
|
trace_context,
|
||||||
|
request_id,
|
||||||
|
} => {
|
||||||
|
self.as_mut().cancel_request(&trace_context, request_id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => return Poll::Ready(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Req, Resp, T> Sink<Response<Resp>> for BaseChannel<Req, Resp, T>
|
||||||
|
where
|
||||||
|
T: Transport<Response<Resp>, ClientMessage<Req>> + Send + 'static,
|
||||||
|
Req: Send + 'static,
|
||||||
|
Resp: Send + 'static,
|
||||||
|
{
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.transport().poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_send(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
response: Response<Resp>,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
if self
|
||||||
|
.as_mut()
|
||||||
|
.in_flight_requests()
|
||||||
|
.remove(&response.request_id)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
self.as_mut().in_flight_requests().compact(0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.transport().start_send(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.transport().poll_flush(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.transport().poll_close(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Req, Resp, T> AsRef<T> for BaseChannel<Req, Resp, T> {
|
||||||
|
fn as_ref(&self) -> &T {
|
||||||
|
self.transport.get_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Req, Resp, T> Channel for BaseChannel<Req, Resp, T>
|
||||||
|
where
|
||||||
|
T: Transport<Response<Resp>, ClientMessage<Req>> + Send + 'static,
|
||||||
|
Req: Send + 'static,
|
||||||
|
Resp: Send + 'static,
|
||||||
|
{
|
||||||
|
type Req = Req;
|
||||||
|
type Resp = Resp;
|
||||||
|
|
||||||
|
fn config(&self) -> &Config {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_flight_requests(mut self: Pin<&mut Self>) -> usize {
|
||||||
|
self.as_mut().in_flight_requests().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_request(self: Pin<&mut Self>, request_id: u64) -> AbortRegistration {
|
||||||
|
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||||
|
assert!(self
|
||||||
|
.in_flight_requests()
|
||||||
|
.insert(request_id, abort_handle)
|
||||||
|
.is_none());
|
||||||
|
abort_registration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A running handler serving all requests coming over a channel.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ClientHandler<Req, Resp, T, F> {
|
pub struct ResponseHandler<C, F>
|
||||||
channel: Channel<Req, Resp, T>,
|
where
|
||||||
|
C: Channel,
|
||||||
|
{
|
||||||
|
channel: C,
|
||||||
/// Responses waiting to be written to the wire.
|
/// Responses waiting to be written to the wire.
|
||||||
pending_responses: Fuse<mpsc::Receiver<(context::Context, Response<Resp>)>>,
|
pending_responses: Fuse<mpsc::Receiver<(context::Context, Response<C::Resp>)>>,
|
||||||
/// Handed out to request handlers to fan in responses.
|
/// Handed out to request handlers to fan in responses.
|
||||||
responses_tx: mpsc::Sender<(context::Context, Response<Resp>)>,
|
responses_tx: mpsc::Sender<(context::Context, Response<C::Resp>)>,
|
||||||
/// Number of requests currently being responded to.
|
|
||||||
in_flight_requests: FnvHashMap<u64, AbortHandle>,
|
|
||||||
/// Request handler.
|
/// Request handler.
|
||||||
f: F,
|
f: F,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Req, Resp, T, F> ClientHandler<Req, Resp, T, F> {
|
impl<C, F> ResponseHandler<C, F>
|
||||||
unsafe_pinned!(channel: Channel<Req, Resp, T>);
|
where
|
||||||
unsafe_pinned!(in_flight_requests: FnvHashMap<u64, AbortHandle>);
|
C: Channel,
|
||||||
unsafe_pinned!(pending_responses: Fuse<mpsc::Receiver<(context::Context, Response<Resp>)>>);
|
{
|
||||||
unsafe_pinned!(responses_tx: mpsc::Sender<(context::Context, Response<Resp>)>);
|
unsafe_pinned!(channel: C);
|
||||||
|
unsafe_pinned!(pending_responses: Fuse<mpsc::Receiver<(context::Context, Response<C::Resp>)>>);
|
||||||
|
unsafe_pinned!(responses_tx: mpsc::Sender<(context::Context, Response<C::Resp>)>);
|
||||||
// For this to be safe, field f must be private, and code in this module must never
|
// For this to be safe, field f must be private, and code in this module must never
|
||||||
// construct PinMut<F>.
|
// construct PinMut<F>.
|
||||||
unsafe_unpinned!(f: F);
|
unsafe_unpinned!(f: F);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Req, Resp, T, F, Fut> ClientHandler<Req, Resp, T, F>
|
impl<C, F, Fut> ResponseHandler<C, F>
|
||||||
where
|
where
|
||||||
Req: Send + 'static,
|
C: Channel,
|
||||||
Resp: Send + 'static,
|
F: FnOnce(context::Context, C::Req) -> Fut + Send + 'static + Clone,
|
||||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
Fut: Future<Output = io::Result<C::Resp>> + Send + 'static,
|
||||||
F: FnOnce(context::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(
|
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
cx: &mut Context<'_>,
|
|
||||||
) -> Poll<io::Result<()>> {
|
|
||||||
if self.in_flight_requests.len()
|
|
||||||
>= self.channel.config.max_in_flight_requests_per_connection
|
|
||||||
{
|
|
||||||
let peer = self.as_mut().channel().client_addr;
|
|
||||||
|
|
||||||
while let Poll::Pending = self.as_mut().channel().poll_ready(cx)? {
|
|
||||||
info!(
|
|
||||||
"[{}] In-flight requests at max ({}), and transport is not ready.",
|
|
||||||
peer,
|
|
||||||
self.as_mut().in_flight_requests().len(),
|
|
||||||
);
|
|
||||||
ready!(self.as_mut().channel().poll_flush(cx)?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pump_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> PollIo<()> {
|
fn pump_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> PollIo<()> {
|
||||||
ready!(self.as_mut().poll_ready_if_throttling(cx)?);
|
match ready!(self.as_mut().channel().poll_next(cx)?) {
|
||||||
|
Some(request) => {
|
||||||
Poll::Ready(match ready!(self.as_mut().channel().poll_next(cx)?) {
|
self.handle_request(request)?;
|
||||||
Some(message) => {
|
Poll::Ready(Some(Ok(())))
|
||||||
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 => {
|
None => Poll::Ready(None),
|
||||||
trace!("[{}] Read half closed", self.channel.client_addr);
|
}
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pump_write(
|
fn pump_write(
|
||||||
@@ -367,7 +456,12 @@ where
|
|||||||
read_half_closed: bool,
|
read_half_closed: bool,
|
||||||
) -> PollIo<()> {
|
) -> PollIo<()> {
|
||||||
match self.as_mut().poll_next_response(cx)? {
|
match self.as_mut().poll_next_response(cx)? {
|
||||||
Poll::Ready(Some((_, response))) => {
|
Poll::Ready(Some((ctx, response))) => {
|
||||||
|
trace!(
|
||||||
|
"[{}] Staging response. In-flight requests = {}.",
|
||||||
|
ctx.trace_id(),
|
||||||
|
self.as_mut().channel().in_flight_requests(),
|
||||||
|
);
|
||||||
self.as_mut().channel().start_send(response)?;
|
self.as_mut().channel().start_send(response)?;
|
||||||
Poll::Ready(Some(Ok(())))
|
Poll::Ready(Some(Ok(())))
|
||||||
}
|
}
|
||||||
@@ -383,7 +477,7 @@ where
|
|||||||
// Being here means there are no staged requests and all written responses are
|
// 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
|
// fully flushed. So, if the read half is closed and there are no in-flight
|
||||||
// requests, then we can close the write half.
|
// requests, then we can close the write half.
|
||||||
if read_half_closed && self.as_mut().in_flight_requests().is_empty() {
|
if read_half_closed && self.as_mut().channel().in_flight_requests() == 0 {
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
} else {
|
} else {
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
@@ -395,90 +489,33 @@ where
|
|||||||
fn poll_next_response(
|
fn poll_next_response(
|
||||||
mut self: Pin<&mut Self>,
|
mut self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
) -> PollIo<(context::Context, Response<Resp>)> {
|
) -> PollIo<(context::Context, Response<C::Resp>)> {
|
||||||
// Ensure there's room to write a response.
|
// Ensure there's room to write a response.
|
||||||
while let Poll::Pending = self.as_mut().channel().poll_ready(cx)? {
|
while let Poll::Pending = self.as_mut().channel().poll_ready(cx)? {
|
||||||
ready!(self.as_mut().channel().poll_flush(cx)?);
|
ready!(self.as_mut().channel().poll_flush(cx)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let peer = self.as_mut().channel().client_addr;
|
|
||||||
|
|
||||||
match ready!(self.as_mut().pending_responses().poll_next(cx)) {
|
match ready!(self.as_mut().pending_responses().poll_next(cx)) {
|
||||||
Some((ctx, response)) => {
|
Some((ctx, response)) => Poll::Ready(Some(Ok((ctx, response)))),
|
||||||
if self
|
|
||||||
.as_mut()
|
|
||||||
.in_flight_requests()
|
|
||||||
.remove(&response.request_id)
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
self.as_mut().in_flight_requests().compact(0.1);
|
|
||||||
}
|
|
||||||
trace!(
|
|
||||||
"[{}/{}] Staging response. In-flight requests = {}.",
|
|
||||||
ctx.trace_id(),
|
|
||||||
peer,
|
|
||||||
self.as_mut().in_flight_requests().len(),
|
|
||||||
);
|
|
||||||
Poll::Ready(Some(Ok((ctx, response))))
|
|
||||||
}
|
|
||||||
None => {
|
None => {
|
||||||
// This branch likely won't happen, since the ClientHandler is holding a Sender.
|
// This branch likely won't happen, since the ResponseHandler is holding a Sender.
|
||||||
trace!("[{}] No new responses.", peer);
|
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_request(
|
fn handle_request(mut self: Pin<&mut Self>, request: Request<C::Req>) -> io::Result<()> {
|
||||||
mut self: Pin<&mut Self>,
|
|
||||||
trace_context: trace::Context,
|
|
||||||
request: Request<Req>,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
let request_id = request.id;
|
let request_id = request.id;
|
||||||
let peer = self.as_mut().channel().client_addr;
|
let deadline = request.context.deadline;
|
||||||
let ctx = context::Context {
|
|
||||||
deadline: request.deadline,
|
|
||||||
trace_context,
|
|
||||||
};
|
|
||||||
let request = request.message;
|
|
||||||
|
|
||||||
if self.as_mut().in_flight_requests().len()
|
|
||||||
>= self
|
|
||||||
.as_mut()
|
|
||||||
.channel()
|
|
||||||
.config
|
|
||||||
.max_in_flight_requests_per_connection
|
|
||||||
{
|
|
||||||
debug!(
|
|
||||||
"[{}/{}] Client has reached in-flight request limit ({}/{}).",
|
|
||||||
ctx.trace_id(),
|
|
||||||
peer,
|
|
||||||
self.as_mut().in_flight_requests().len(),
|
|
||||||
self.as_mut()
|
|
||||||
.channel()
|
|
||||||
.config
|
|
||||||
.max_in_flight_requests_per_connection
|
|
||||||
);
|
|
||||||
|
|
||||||
self.as_mut().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();
|
let timeout = deadline.as_duration();
|
||||||
trace!(
|
trace!(
|
||||||
"[{}/{}] Received request with deadline {} (timeout {:?}).",
|
"[{}] Received request with deadline {} (timeout {:?}).",
|
||||||
ctx.trace_id(),
|
request.context.trace_id(),
|
||||||
peer,
|
|
||||||
format_rfc3339(deadline),
|
format_rfc3339(deadline),
|
||||||
timeout,
|
timeout,
|
||||||
);
|
);
|
||||||
|
let ctx = request.context;
|
||||||
|
let request = request.message;
|
||||||
let mut response_tx = self.as_mut().responses_tx().clone();
|
let mut response_tx = self.as_mut().responses_tx().clone();
|
||||||
|
|
||||||
let trace_id = *ctx.trace_id();
|
let trace_id = *ctx.trace_id();
|
||||||
@@ -489,18 +526,19 @@ where
|
|||||||
request_id,
|
request_id,
|
||||||
message: match result {
|
message: match result {
|
||||||
Ok(message) => Ok(message),
|
Ok(message) => Ok(message),
|
||||||
Err(e) => Err(make_server_error(e, trace_id, peer, deadline)),
|
Err(e) => Err(make_server_error(e, trace_id, deadline)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
trace!("[{}/{}] Sending response.", trace_id, peer);
|
trace!("[{}] Sending response.", trace_id);
|
||||||
response_tx
|
response_tx
|
||||||
.send((ctx, response))
|
.send((ctx, response))
|
||||||
.unwrap_or_else(|_| ())
|
.unwrap_or_else(|_| ())
|
||||||
.await;
|
.await;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let (abortable_response, abort_handle) = abortable(response);
|
let abort_registration = self.as_mut().channel().start_request(request_id);
|
||||||
crate::spawn(abortable_response.map(|_| ())).map_err(|e| {
|
let response = Abortable::new(response, abort_registration);
|
||||||
|
crate::spawn(response.map(|_| ())).map_err(|e| {
|
||||||
io::Error::new(
|
io::Error::new(
|
||||||
io::ErrorKind::Other,
|
io::ErrorKind::Other,
|
||||||
format!(
|
format!(
|
||||||
@@ -509,92 +547,49 @@ where
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
self.as_mut()
|
|
||||||
.in_flight_requests()
|
|
||||||
.insert(request_id, abort_handle);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancel_request(mut self: 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.as_mut().in_flight_requests().remove(&request_id) {
|
|
||||||
self.as_mut().in_flight_requests().compact(0.1);
|
|
||||||
|
|
||||||
cancel_handle.abort();
|
|
||||||
let remaining = self.as_mut().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>
|
impl<C, F, Fut> Future for ResponseHandler<C, F>
|
||||||
where
|
where
|
||||||
Req: Send + 'static,
|
C: Channel,
|
||||||
Resp: Send + 'static,
|
F: FnOnce(context::Context, C::Req) -> Fut + Send + 'static + Clone,
|
||||||
T: Transport<Item = ClientMessage<Req>, SinkItem = Response<Resp>> + Send,
|
Fut: Future<Output = io::Result<C::Resp>> + Send + 'static,
|
||||||
F: FnOnce(context::Context, Req) -> Fut + Send + 'static + Clone,
|
|
||||||
Fut: Future<Output = io::Result<Resp>> + Send + 'static,
|
|
||||||
{
|
{
|
||||||
type Output = io::Result<()>;
|
type Output = ();
|
||||||
|
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||||
trace!("[{}] ClientHandler::poll", self.channel.client_addr);
|
move || -> Poll<io::Result<()>> {
|
||||||
loop {
|
loop {
|
||||||
let read = self.as_mut().pump_read(cx)?;
|
let read = self.as_mut().pump_read(cx)?;
|
||||||
match (
|
match (
|
||||||
read,
|
read,
|
||||||
self.as_mut().pump_write(cx, read == Poll::Ready(None))?,
|
self.as_mut().pump_write(cx, read == Poll::Ready(None))?,
|
||||||
) {
|
) {
|
||||||
(Poll::Ready(None), Poll::Ready(None)) => {
|
(Poll::Ready(None), Poll::Ready(None)) => {
|
||||||
info!("[{}] Client disconnected.", self.channel.client_addr);
|
return Poll::Ready(Ok(()));
|
||||||
return Poll::Ready(Ok(()));
|
}
|
||||||
}
|
(Poll::Ready(Some(())), _) | (_, Poll::Ready(Some(()))) => {}
|
||||||
(read @ Poll::Ready(Some(())), write) | (read, write @ Poll::Ready(Some(()))) => {
|
_ => {
|
||||||
trace!(
|
return Poll::Pending;
|
||||||
"[{}] read: {:?}, write: {:?}.",
|
}
|
||||||
self.channel.client_addr,
|
|
||||||
read,
|
|
||||||
write
|
|
||||||
)
|
|
||||||
}
|
|
||||||
(read, write) => {
|
|
||||||
trace!(
|
|
||||||
"[{}] read: {:?}, write: {:?} (not ready).",
|
|
||||||
self.channel.client_addr,
|
|
||||||
read,
|
|
||||||
write,
|
|
||||||
);
|
|
||||||
return Poll::Pending;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}()
|
||||||
|
.map(|r| r.unwrap_or_else(|e| info!("ResponseHandler errored out: {}", e)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_server_error(
|
fn make_server_error(
|
||||||
e: timeout::Error<io::Error>,
|
e: timeout::Error<io::Error>,
|
||||||
trace_id: TraceId,
|
trace_id: TraceId,
|
||||||
peer: SocketAddr,
|
|
||||||
deadline: SystemTime,
|
deadline: SystemTime,
|
||||||
) -> ServerError {
|
) -> ServerError {
|
||||||
if e.is_elapsed() {
|
if e.is_elapsed() {
|
||||||
debug!(
|
debug!(
|
||||||
"[{}/{}] Response did not complete before deadline of {}s.",
|
"[{}] Response did not complete before deadline of {}s.",
|
||||||
trace_id,
|
trace_id,
|
||||||
peer,
|
|
||||||
format_rfc3339(deadline)
|
format_rfc3339(deadline)
|
||||||
);
|
);
|
||||||
// No point in responding, since the client will have dropped the request.
|
// No point in responding, since the client will have dropped the request.
|
||||||
@@ -607,8 +602,8 @@ fn make_server_error(
|
|||||||
}
|
}
|
||||||
} else if e.is_timer() {
|
} else if e.is_timer() {
|
||||||
error!(
|
error!(
|
||||||
"[{}/{}] Response failed because of an issue with a timer: {}",
|
"[{}] Response failed because of an issue with a timer: {}",
|
||||||
trace_id, peer, e
|
trace_id, e
|
||||||
);
|
);
|
||||||
|
|
||||||
ServerError {
|
ServerError {
|
||||||
@@ -622,7 +617,7 @@ fn make_server_error(
|
|||||||
detail: Some(e.description().into()),
|
detail: Some(e.description().into()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error!("[{}/{}] Unexpected response failure: {}", trace_id, peer, e);
|
error!("[{}] Unexpected response failure: {}", trace_id, e);
|
||||||
|
|
||||||
ServerError {
|
ServerError {
|
||||||
kind: io::ErrorKind::Other,
|
kind: io::ErrorKind::Other,
|
||||||
|
|||||||
125
rpc/src/server/testing.rs
Normal file
125
rpc/src/server/testing.rs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
use crate::server::{Channel, Config};
|
||||||
|
use crate::{context, Request, Response};
|
||||||
|
use fnv::FnvHashSet;
|
||||||
|
use futures::future::{AbortHandle, AbortRegistration};
|
||||||
|
use futures::{Sink, Stream};
|
||||||
|
use futures_test::task::noop_waker_ref;
|
||||||
|
use pin_utils::{unsafe_pinned, unsafe_unpinned};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::io;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
pub(crate) struct FakeChannel<In, Out> {
|
||||||
|
pub stream: VecDeque<In>,
|
||||||
|
pub sink: VecDeque<Out>,
|
||||||
|
pub config: Config,
|
||||||
|
pub in_flight_requests: FnvHashSet<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<In, Out> FakeChannel<In, Out> {
|
||||||
|
unsafe_pinned!(stream: VecDeque<In>);
|
||||||
|
unsafe_pinned!(sink: VecDeque<Out>);
|
||||||
|
unsafe_unpinned!(in_flight_requests: FnvHashSet<u64>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<In, Out> Stream for FakeChannel<In, Out>
|
||||||
|
where
|
||||||
|
In: Unpin,
|
||||||
|
{
|
||||||
|
type Item = In;
|
||||||
|
|
||||||
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||||
|
self.stream().poll_next(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<In, Resp> Sink<Response<Resp>> for FakeChannel<In, Response<Resp>> {
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.sink().poll_ready(cx).map_err(|e| match e {})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_send(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
response: Response<Resp>,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
self.as_mut()
|
||||||
|
.in_flight_requests()
|
||||||
|
.remove(&response.request_id);
|
||||||
|
self.sink().start_send(response).map_err(|e| match e {})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.sink().poll_flush(cx).map_err(|e| match e {})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.sink().poll_close(cx).map_err(|e| match e {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Req, Resp> Channel for FakeChannel<io::Result<Request<Req>>, Response<Resp>>
|
||||||
|
where
|
||||||
|
Req: Unpin + Send + 'static,
|
||||||
|
Resp: Send + 'static,
|
||||||
|
{
|
||||||
|
type Req = Req;
|
||||||
|
type Resp = Resp;
|
||||||
|
|
||||||
|
fn config(&self) -> &Config {
|
||||||
|
&self.config
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_flight_requests(self: Pin<&mut Self>) -> usize {
|
||||||
|
self.in_flight_requests.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_request(self: Pin<&mut Self>, id: u64) -> AbortRegistration {
|
||||||
|
self.in_flight_requests().insert(id);
|
||||||
|
AbortHandle::new_pair().1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Req, Resp> FakeChannel<io::Result<Request<Req>>, Response<Resp>> {
|
||||||
|
pub fn push_req(&mut self, id: u64, message: Req) {
|
||||||
|
self.stream.push_back(Ok(Request {
|
||||||
|
context: context::Context {
|
||||||
|
deadline: SystemTime::UNIX_EPOCH,
|
||||||
|
trace_context: Default::default(),
|
||||||
|
},
|
||||||
|
id,
|
||||||
|
message,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FakeChannel<(), ()> {
|
||||||
|
pub fn default<Req, Resp>() -> FakeChannel<io::Result<Request<Req>>, Response<Resp>> {
|
||||||
|
FakeChannel {
|
||||||
|
stream: VecDeque::default(),
|
||||||
|
sink: VecDeque::default(),
|
||||||
|
config: Config::default(),
|
||||||
|
in_flight_requests: FnvHashSet::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PollExt {
|
||||||
|
fn is_done(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PollExt for Poll<Option<T>> {
|
||||||
|
fn is_done(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Poll::Ready(None) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cx() -> Context<'static> {
|
||||||
|
Context::from_waker(&noop_waker_ref())
|
||||||
|
}
|
||||||
332
rpc/src/server/throttle.rs
Normal file
332
rpc/src/server/throttle.rs
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
use super::{Channel, Config};
|
||||||
|
use crate::{Response, ServerError};
|
||||||
|
use futures::{
|
||||||
|
future::AbortRegistration,
|
||||||
|
prelude::*,
|
||||||
|
ready,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
use log::debug;
|
||||||
|
use pin_utils::{unsafe_pinned, unsafe_unpinned};
|
||||||
|
use std::{io, pin::Pin};
|
||||||
|
|
||||||
|
/// A [`Channel`] that limits the number of concurrent
|
||||||
|
/// requests by throttling.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Throttler<C> {
|
||||||
|
max_in_flight_requests: usize,
|
||||||
|
inner: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Throttler<C> {
|
||||||
|
unsafe_unpinned!(max_in_flight_requests: usize);
|
||||||
|
unsafe_pinned!(inner: C);
|
||||||
|
|
||||||
|
/// Returns the inner channel.
|
||||||
|
pub fn get_ref(&self) -> &C {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Throttler<C>
|
||||||
|
where
|
||||||
|
C: Channel,
|
||||||
|
{
|
||||||
|
/// Returns a new `Throttler` that wraps the given channel and limits concurrent requests to
|
||||||
|
/// `max_in_flight_requests`.
|
||||||
|
pub fn new(inner: C, max_in_flight_requests: usize) -> Self {
|
||||||
|
Throttler {
|
||||||
|
inner,
|
||||||
|
max_in_flight_requests,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Stream for Throttler<C>
|
||||||
|
where
|
||||||
|
C: Channel,
|
||||||
|
{
|
||||||
|
type Item = <C as Stream>::Item;
|
||||||
|
|
||||||
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||||
|
while self.as_mut().in_flight_requests() >= *self.as_mut().max_in_flight_requests() {
|
||||||
|
ready!(self.as_mut().inner().poll_ready(cx)?);
|
||||||
|
|
||||||
|
match ready!(self.as_mut().inner().poll_next(cx)?) {
|
||||||
|
Some(request) => {
|
||||||
|
debug!(
|
||||||
|
"[{}] Client has reached in-flight request limit ({}/{}).",
|
||||||
|
request.context.trace_id(),
|
||||||
|
self.as_mut().in_flight_requests(),
|
||||||
|
self.as_mut().max_in_flight_requests(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.as_mut().start_send(Response {
|
||||||
|
request_id: request.id,
|
||||||
|
message: Err(ServerError {
|
||||||
|
kind: io::ErrorKind::WouldBlock,
|
||||||
|
detail: Some("Server throttled the request.".into()),
|
||||||
|
}),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
None => return Poll::Ready(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.inner().poll_next(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Sink<Response<<C as Channel>::Resp>> for Throttler<C>
|
||||||
|
where
|
||||||
|
C: Channel,
|
||||||
|
{
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.inner().poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_send(self: Pin<&mut Self>, item: Response<<C as Channel>::Resp>) -> io::Result<()> {
|
||||||
|
self.inner().start_send(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
|
||||||
|
self.inner().poll_flush(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
|
||||||
|
self.inner().poll_close(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> AsRef<C> for Throttler<C> {
|
||||||
|
fn as_ref(&self) -> &C {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Channel for Throttler<C>
|
||||||
|
where
|
||||||
|
C: Channel,
|
||||||
|
{
|
||||||
|
type Req = <C as Channel>::Req;
|
||||||
|
type Resp = <C as Channel>::Resp;
|
||||||
|
|
||||||
|
fn in_flight_requests(self: Pin<&mut Self>) -> usize {
|
||||||
|
self.inner().in_flight_requests()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config(&self) -> &Config {
|
||||||
|
self.inner.config()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_request(self: Pin<&mut Self>, request_id: u64) -> AbortRegistration {
|
||||||
|
self.inner().start_request(request_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A stream of throttling channels.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ThrottlerStream<S> {
|
||||||
|
inner: S,
|
||||||
|
max_in_flight_requests: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> ThrottlerStream<S>
|
||||||
|
where
|
||||||
|
S: Stream,
|
||||||
|
<S as Stream>::Item: Channel,
|
||||||
|
{
|
||||||
|
unsafe_pinned!(inner: S);
|
||||||
|
unsafe_unpinned!(max_in_flight_requests: usize);
|
||||||
|
|
||||||
|
pub(crate) fn new(inner: S, max_in_flight_requests: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
max_in_flight_requests,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Stream for ThrottlerStream<S>
|
||||||
|
where
|
||||||
|
S: Stream,
|
||||||
|
<S as Stream>::Item: Channel,
|
||||||
|
{
|
||||||
|
type Item = Throttler<<S as Stream>::Item>;
|
||||||
|
|
||||||
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||||
|
match ready!(self.as_mut().inner().poll_next(cx)) {
|
||||||
|
Some(channel) => Poll::Ready(Some(Throttler::new(
|
||||||
|
channel,
|
||||||
|
*self.max_in_flight_requests(),
|
||||||
|
))),
|
||||||
|
None => Poll::Ready(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use super::testing::{self, FakeChannel, PollExt};
|
||||||
|
#[cfg(test)]
|
||||||
|
use crate::Request;
|
||||||
|
#[cfg(test)]
|
||||||
|
use pin_utils::pin_mut;
|
||||||
|
#[cfg(test)]
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn throttler_in_flight_requests() {
|
||||||
|
let throttler = Throttler {
|
||||||
|
max_in_flight_requests: 0,
|
||||||
|
inner: FakeChannel::default::<isize, isize>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
pin_mut!(throttler);
|
||||||
|
for i in 0..5 {
|
||||||
|
throttler.inner.in_flight_requests.insert(i);
|
||||||
|
}
|
||||||
|
assert_eq!(throttler.as_mut().in_flight_requests(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn throttler_start_request() {
|
||||||
|
let throttler = Throttler {
|
||||||
|
max_in_flight_requests: 0,
|
||||||
|
inner: FakeChannel::default::<isize, isize>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
pin_mut!(throttler);
|
||||||
|
throttler.as_mut().start_request(1);
|
||||||
|
assert_eq!(throttler.inner.in_flight_requests.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn throttler_poll_next_done() {
|
||||||
|
let throttler = Throttler {
|
||||||
|
max_in_flight_requests: 0,
|
||||||
|
inner: FakeChannel::default::<isize, isize>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
pin_mut!(throttler);
|
||||||
|
assert!(throttler.as_mut().poll_next(&mut testing::cx()).is_done());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn throttler_poll_next_some() -> io::Result<()> {
|
||||||
|
let throttler = Throttler {
|
||||||
|
max_in_flight_requests: 1,
|
||||||
|
inner: FakeChannel::default::<isize, isize>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
pin_mut!(throttler);
|
||||||
|
throttler.inner.push_req(0, 1);
|
||||||
|
assert!(throttler.as_mut().poll_ready(&mut testing::cx()).is_ready());
|
||||||
|
assert_eq!(
|
||||||
|
throttler
|
||||||
|
.as_mut()
|
||||||
|
.poll_next(&mut testing::cx())?
|
||||||
|
.map(|r| r.map(|r| (r.id, r.message))),
|
||||||
|
Poll::Ready(Some((0, 1)))
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn throttler_poll_next_throttled() {
|
||||||
|
let throttler = Throttler {
|
||||||
|
max_in_flight_requests: 0,
|
||||||
|
inner: FakeChannel::default::<isize, isize>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
pin_mut!(throttler);
|
||||||
|
throttler.inner.push_req(1, 1);
|
||||||
|
assert!(throttler.as_mut().poll_next(&mut testing::cx()).is_done());
|
||||||
|
assert_eq!(throttler.inner.sink.len(), 1);
|
||||||
|
let resp = throttler.inner.sink.get(0).unwrap();
|
||||||
|
assert_eq!(resp.request_id, 1);
|
||||||
|
assert!(resp.message.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn throttler_poll_next_throttled_sink_not_ready() {
|
||||||
|
let throttler = Throttler {
|
||||||
|
max_in_flight_requests: 0,
|
||||||
|
inner: PendingSink::default::<isize, isize>(),
|
||||||
|
};
|
||||||
|
pin_mut!(throttler);
|
||||||
|
assert!(throttler.poll_next(&mut testing::cx()).is_pending());
|
||||||
|
|
||||||
|
struct PendingSink<In, Out> {
|
||||||
|
ghost: PhantomData<fn(Out) -> In>,
|
||||||
|
}
|
||||||
|
impl PendingSink<(), ()> {
|
||||||
|
pub fn default<Req, Resp>() -> PendingSink<io::Result<Request<Req>>, Response<Resp>> {
|
||||||
|
PendingSink { ghost: PhantomData }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<In, Out> Stream for PendingSink<In, Out> {
|
||||||
|
type Item = In;
|
||||||
|
fn poll_next(self: Pin<&mut Self>, _: &mut Context) -> Poll<Option<Self::Item>> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<In, Out> Sink<Out> for PendingSink<In, Out> {
|
||||||
|
type Error = io::Error;
|
||||||
|
fn poll_ready(self: Pin<&mut Self>, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
fn start_send(self: Pin<&mut Self>, _: Out) -> Result<(), Self::Error> {
|
||||||
|
Err(io::Error::from(io::ErrorKind::WouldBlock))
|
||||||
|
}
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
fn poll_close(self: Pin<&mut Self>, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<Req, Resp> Channel for PendingSink<io::Result<Request<Req>>, Response<Resp>>
|
||||||
|
where
|
||||||
|
Req: Send + 'static,
|
||||||
|
Resp: Send + 'static,
|
||||||
|
{
|
||||||
|
type Req = Req;
|
||||||
|
type Resp = Resp;
|
||||||
|
fn config(&self) -> &Config {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
fn in_flight_requests(self: Pin<&mut Self>) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
fn start_request(self: Pin<&mut Self>, _: u64) -> AbortRegistration {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn throttler_start_send() {
|
||||||
|
let throttler = Throttler {
|
||||||
|
max_in_flight_requests: 0,
|
||||||
|
inner: FakeChannel::default::<isize, isize>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
pin_mut!(throttler);
|
||||||
|
throttler.inner.in_flight_requests.insert(0);
|
||||||
|
throttler
|
||||||
|
.as_mut()
|
||||||
|
.start_send(Response {
|
||||||
|
request_id: 0,
|
||||||
|
message: Ok(1),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert!(throttler.inner.in_flight_requests.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
throttler.inner.sink.get(0),
|
||||||
|
Some(&Response {
|
||||||
|
request_id: 0,
|
||||||
|
message: Ok(1),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,14 +6,11 @@
|
|||||||
|
|
||||||
//! Transports backed by in-memory channels.
|
//! Transports backed by in-memory channels.
|
||||||
|
|
||||||
use crate::{PollIo, Transport};
|
use crate::PollIo;
|
||||||
use futures::{channel::mpsc, task::Context, Poll, Sink, Stream};
|
use futures::{channel::mpsc, task::Context, Poll, Sink, Stream};
|
||||||
use pin_utils::unsafe_pinned;
|
use pin_utils::unsafe_pinned;
|
||||||
|
use std::io;
|
||||||
use std::pin::Pin;
|
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
|
/// Returns two unbounded channel peers. Each [`Stream`] yields items sent through the other's
|
||||||
/// [`Sink`].
|
/// [`Sink`].
|
||||||
@@ -78,19 +75,6 @@ impl<Item, SinkItem> Sink<SinkItem> for UnboundedChannel<Item, SinkItem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Item, SinkItem> Transport for UnboundedChannel<Item, SinkItem> {
|
|
||||||
type SinkItem = SinkItem;
|
|
||||||
type Item = Item;
|
|
||||||
|
|
||||||
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -110,7 +94,7 @@ mod tests {
|
|||||||
|
|
||||||
let (client_channel, server_channel) = transport::channel::unbounded();
|
let (client_channel, server_channel) = transport::channel::unbounded();
|
||||||
let server = Server::<String, u64>::default()
|
let server = Server::<String, u64>::default()
|
||||||
.incoming(stream::once(future::ready(Ok(server_channel))))
|
.incoming(stream::once(future::ready(server_channel)))
|
||||||
.respond_with(|_ctx, request| {
|
.respond_with(|_ctx, request| {
|
||||||
future::ready(request.parse::<u64>().map_err(|_| {
|
future::ready(request.parse::<u64>().map_err(|_| {
|
||||||
io::Error::new(
|
io::Error::new(
|
||||||
|
|||||||
@@ -10,114 +10,10 @@
|
|||||||
//! can be plugged in, using whatever protocol it wants.
|
//! can be plugged in, using whatever protocol it wants.
|
||||||
|
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use std::{
|
use std::io;
|
||||||
io,
|
|
||||||
marker::PhantomData,
|
|
||||||
net::SocketAddr,
|
|
||||||
pin::Pin,
|
|
||||||
task::{Context, Poll},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod channel;
|
pub mod channel;
|
||||||
|
|
||||||
/// A bidirectional stream ([`Sink`] + [`Stream`]) of messages.
|
/// A bidirectional stream ([`Sink`] + [`Stream`]) of messages.
|
||||||
pub trait Transport
|
pub trait Transport<SinkItem, Item> =
|
||||||
where
|
Stream<Item = io::Result<Item>> + Sink<SinkItem, Error = io::Error>;
|
||||||
Self: Stream<Item = io::Result<<Self as Transport>::Item>>,
|
|
||||||
Self: Sink<<Self as Transport>::SinkItem, Error = 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, SinkItem, Item>(
|
|
||||||
inner: S,
|
|
||||||
peer_addr: SocketAddr,
|
|
||||||
local_addr: SocketAddr,
|
|
||||||
) -> impl Transport<Item = Item, SinkItem = SinkItem>
|
|
||||||
where
|
|
||||||
S: Stream<Item = io::Result<Item>>,
|
|
||||||
S: Sink<SinkItem, Error = io::Error>,
|
|
||||||
{
|
|
||||||
TransportShim {
|
|
||||||
inner,
|
|
||||||
peer_addr,
|
|
||||||
local_addr,
|
|
||||||
_marker: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A transport created by adding peers to a Stream + Sink.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct TransportShim<S, SinkItem> {
|
|
||||||
peer_addr: SocketAddr,
|
|
||||||
local_addr: SocketAddr,
|
|
||||||
inner: S,
|
|
||||||
_marker: PhantomData<SinkItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, SinkItem> TransportShim<S, SinkItem> {
|
|
||||||
pin_utils::unsafe_pinned!(inner: S);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, SinkItem> Stream for TransportShim<S, SinkItem>
|
|
||||||
where
|
|
||||||
S: Stream,
|
|
||||||
{
|
|
||||||
type Item = S::Item;
|
|
||||||
|
|
||||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<S::Item>> {
|
|
||||||
self.inner().poll_next(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, Item> Sink<Item> for TransportShim<S, Item>
|
|
||||||
where
|
|
||||||
S: Sink<Item>,
|
|
||||||
{
|
|
||||||
type Error = S::Error;
|
|
||||||
|
|
||||||
fn start_send(self: Pin<&mut Self>, item: Item) -> Result<(), S::Error> {
|
|
||||||
self.inner().start_send(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), S::Error>> {
|
|
||||||
self.inner().poll_ready(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), S::Error>> {
|
|
||||||
self.inner().poll_flush(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), S::Error>> {
|
|
||||||
self.inner().poll_close(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, SinkItem, Item> Transport for TransportShim<S, SinkItem>
|
|
||||||
where
|
|
||||||
S: Stream + Sink<SinkItem>,
|
|
||||||
Self: Stream<Item = io::Result<Item>>,
|
|
||||||
Self: Sink<SinkItem, Error = io::Error>,
|
|
||||||
{
|
|
||||||
type Item = Item;
|
|
||||||
type SinkItem = 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -38,9 +38,11 @@ where
|
|||||||
H: BuildHasher,
|
H: BuildHasher,
|
||||||
{
|
{
|
||||||
fn compact(&mut self, usage_ratio_threshold: f64) {
|
fn compact(&mut self, usage_ratio_threshold: f64) {
|
||||||
let usage_ratio = self.len() as f64 / self.capacity() as f64;
|
if self.capacity() > 1000 {
|
||||||
if usage_ratio < usage_ratio_threshold {
|
let usage_ratio = self.len() as f64 / self.capacity() as f64;
|
||||||
self.shrink_to_fit();
|
if usage_ratio < usage_ratio_threshold {
|
||||||
|
self.shrink_to_fit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use futures::{
|
|||||||
};
|
};
|
||||||
use rpc::{
|
use rpc::{
|
||||||
client, context,
|
client, context,
|
||||||
server::{self, Handler, Server},
|
server::{self, Handler},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
@@ -60,8 +60,9 @@ impl subscriber::Service for Subscriber {
|
|||||||
|
|
||||||
impl Subscriber {
|
impl Subscriber {
|
||||||
async fn listen(id: u32, config: server::Config) -> io::Result<SocketAddr> {
|
async fn listen(id: u32, config: server::Config) -> io::Result<SocketAddr> {
|
||||||
let incoming = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
let incoming = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?
|
||||||
let addr = incoming.local_addr();
|
.filter_map(|r| future::ready(r.ok()));
|
||||||
|
let addr = incoming.get_ref().local_addr();
|
||||||
tokio_executor::spawn(
|
tokio_executor::spawn(
|
||||||
server::new(config)
|
server::new(config)
|
||||||
.incoming(incoming)
|
.incoming(incoming)
|
||||||
@@ -140,12 +141,13 @@ impl publisher::Service for Publisher {
|
|||||||
|
|
||||||
async fn run() -> io::Result<()> {
|
async fn run() -> io::Result<()> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
let transport = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
let transport = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?
|
||||||
let publisher_addr = transport.local_addr();
|
.filter_map(|r| future::ready(r.ok()));
|
||||||
|
let publisher_addr = transport.get_ref().local_addr();
|
||||||
tokio_executor::spawn(
|
tokio_executor::spawn(
|
||||||
Server::default()
|
transport
|
||||||
.incoming(transport)
|
|
||||||
.take(1)
|
.take(1)
|
||||||
|
.map(server::BaseChannel::with_defaults)
|
||||||
.respond_with(publisher::serve(Publisher::new()))
|
.respond_with(publisher::serve(Publisher::new()))
|
||||||
.unit_error()
|
.unit_error()
|
||||||
.boxed()
|
.boxed()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use futures::{
|
|||||||
};
|
};
|
||||||
use rpc::{
|
use rpc::{
|
||||||
client, context,
|
client, context,
|
||||||
server::{Handler, Server},
|
server::{BaseChannel, Channel},
|
||||||
};
|
};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
@@ -43,20 +43,22 @@ impl Service for HelloServer {
|
|||||||
async fn run() -> io::Result<()> {
|
async fn run() -> io::Result<()> {
|
||||||
// bincode_transport is provided by the associated crate bincode-transport. It makes it easy
|
// 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.
|
// to start up a serde-powered bincode serialization strategy over TCP.
|
||||||
let transport = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
let mut transport = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
||||||
let addr = transport.local_addr();
|
let addr = transport.local_addr();
|
||||||
|
|
||||||
// The server is configured with the defaults.
|
// For this example, we're just going to wait for one connection.
|
||||||
let server = Server::default()
|
let client = transport.next().await.unwrap()?;
|
||||||
// Server can listen on any type that implements the Transport trait.
|
|
||||||
.incoming(transport)
|
// `Channel` is a trait representing a server-side connection. It is a trait to allow
|
||||||
// Close the stream after the client connects
|
// for some channels to be instrumented: for example, to track the number of open connections.
|
||||||
.take(1)
|
// BaseChannel is the most basic channel, simply wrapping a transport with no added
|
||||||
|
// functionality.
|
||||||
|
let server = BaseChannel::with_defaults(client)
|
||||||
// serve is generated by the tarpc::service! macro. It takes as input any type implementing
|
// serve is generated by the tarpc::service! macro. It takes as input any type implementing
|
||||||
// the generated Service trait.
|
// the generated Service trait.
|
||||||
.respond_with(serve(HelloServer));
|
.respond_with(serve(HelloServer));
|
||||||
|
|
||||||
tokio_executor::spawn(server.unit_error().boxed().compat());
|
tokio::spawn(server.unit_error().boxed().compat());
|
||||||
|
|
||||||
let transport = bincode_transport::connect(&addr).await?;
|
let transport = bincode_transport::connect(&addr).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -69,8 +69,9 @@ impl DoubleService for DoubleServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn run() -> io::Result<()> {
|
async fn run() -> io::Result<()> {
|
||||||
let add_listener = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
let add_listener = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?
|
||||||
let addr = add_listener.local_addr();
|
.filter_map(|r| future::ready(r.ok()));
|
||||||
|
let addr = add_listener.get_ref().local_addr();
|
||||||
let add_server = Server::default()
|
let add_server = Server::default()
|
||||||
.incoming(add_listener)
|
.incoming(add_listener)
|
||||||
.take(1)
|
.take(1)
|
||||||
@@ -80,8 +81,9 @@ async fn run() -> io::Result<()> {
|
|||||||
let to_add_server = bincode_transport::connect(&addr).await?;
|
let to_add_server = bincode_transport::connect(&addr).await?;
|
||||||
let add_client = add::new_stub(client::Config::default(), to_add_server).await?;
|
let add_client = add::new_stub(client::Config::default(), to_add_server).await?;
|
||||||
|
|
||||||
let double_listener = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
let double_listener = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?
|
||||||
let addr = double_listener.local_addr();
|
.filter_map(|r| future::ready(r.ok()));
|
||||||
|
let addr = double_listener.get_ref().local_addr();
|
||||||
let double_server = rpc::Server::default()
|
let double_server = rpc::Server::default()
|
||||||
.incoming(double_listener)
|
.incoming(double_listener)
|
||||||
.take(1)
|
.take(1)
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
#![feature(
|
#![feature(async_await, arbitrary_self_types, proc_macro_hygiene)]
|
||||||
async_await,
|
|
||||||
arbitrary_self_types,
|
|
||||||
proc_macro_hygiene,
|
|
||||||
impl_trait_in_bindings
|
|
||||||
)]
|
|
||||||
|
|
||||||
mod registry {
|
mod registry {
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
@@ -382,8 +377,9 @@ async fn run() -> io::Result<()> {
|
|||||||
read_service::serve(server.clone()),
|
read_service::serve(server.clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let listener = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?;
|
let listener = bincode_transport::listen(&"0.0.0.0:0".parse().unwrap())?
|
||||||
let server_addr = listener.local_addr();
|
.filter_map(|r| future::ready(r.ok()));
|
||||||
|
let server_addr = listener.get_ref().local_addr();
|
||||||
let server = tarpc::Server::default()
|
let server = tarpc::Server::default()
|
||||||
.incoming(listener)
|
.incoming(listener)
|
||||||
.take(1)
|
.take(1)
|
||||||
|
|||||||
@@ -58,13 +58,13 @@ macro_rules! service {
|
|||||||
(
|
(
|
||||||
$(
|
$(
|
||||||
$(#[$attr:meta])*
|
$(#[$attr:meta])*
|
||||||
rpc $fn_name:ident( $( $arg:ident : $in_:ty ),* ) $(-> $out:ty)*;
|
rpc $fn_name:ident( $( $(#[$argattr:meta])* $arg:ident : $in_:ty ),* ) $(-> $out:ty)*;
|
||||||
)*
|
)*
|
||||||
) => {
|
) => {
|
||||||
$crate::service! {{
|
$crate::service! {{
|
||||||
$(
|
$(
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
rpc $fn_name( $( $arg : $in_ ),* ) $(-> $out)*;
|
rpc $fn_name( $( $(#[$argattr])* $arg : $in_ ),* ) $(-> $out)*;
|
||||||
)*
|
)*
|
||||||
}}
|
}}
|
||||||
};
|
};
|
||||||
@@ -72,7 +72,7 @@ macro_rules! service {
|
|||||||
(
|
(
|
||||||
{
|
{
|
||||||
$(#[$attr:meta])*
|
$(#[$attr:meta])*
|
||||||
rpc $fn_name:ident( $( $arg:ident : $in_:ty ),* );
|
rpc $fn_name:ident( $( $(#[$argattr:meta])* $arg:ident : $in_:ty ),* );
|
||||||
|
|
||||||
$( $unexpanded:tt )*
|
$( $unexpanded:tt )*
|
||||||
}
|
}
|
||||||
@@ -84,14 +84,14 @@ macro_rules! service {
|
|||||||
$( $expanded )*
|
$( $expanded )*
|
||||||
|
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
rpc $fn_name( $( $arg : $in_ ),* ) -> ();
|
rpc $fn_name( $( $(#[$argattr])* $arg : $in_ ),* ) -> ();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Pattern for when the next rpc has an explicit return type.
|
// Pattern for when the next rpc has an explicit return type.
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
$(#[$attr:meta])*
|
$(#[$attr:meta])*
|
||||||
rpc $fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty;
|
rpc $fn_name:ident( $( $(#[$argattr:meta])* $arg:ident : $in_:ty ),* ) -> $out:ty;
|
||||||
|
|
||||||
$( $unexpanded:tt )*
|
$( $unexpanded:tt )*
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ macro_rules! service {
|
|||||||
$( $expanded )*
|
$( $expanded )*
|
||||||
|
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
rpc $fn_name( $( $arg : $in_ ),* ) -> $out;
|
rpc $fn_name( $( $(#[$argattr])* $arg : $in_ ),* ) -> $out;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Pattern for when all return types have been expanded
|
// Pattern for when all return types have been expanded
|
||||||
@@ -111,7 +111,7 @@ macro_rules! service {
|
|||||||
{ } // none left to expand
|
{ } // none left to expand
|
||||||
$(
|
$(
|
||||||
$(#[$attr:meta])*
|
$(#[$attr:meta])*
|
||||||
rpc $fn_name:ident ( $( $arg:ident : $in_:ty ),* ) -> $out:ty;
|
rpc $fn_name:ident ( $( $(#[$argattr:meta])* $arg:ident : $in_:ty ),* ) -> $out:ty;
|
||||||
)*
|
)*
|
||||||
) => {
|
) => {
|
||||||
$crate::add_serde_if_enabled! {
|
$crate::add_serde_if_enabled! {
|
||||||
@@ -122,7 +122,7 @@ macro_rules! service {
|
|||||||
pub enum Request {
|
pub enum Request {
|
||||||
$(
|
$(
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
$fn_name{ $($arg: $in_,)* }
|
$fn_name{ $( $(#[$argattr])* $arg: $in_,)* }
|
||||||
),*
|
),*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,9 +218,7 @@ macro_rules! service {
|
|||||||
pub async fn new_stub<T>(config: $crate::client::Config, transport: T)
|
pub async fn new_stub<T>(config: $crate::client::Config, transport: T)
|
||||||
-> ::std::io::Result<Client>
|
-> ::std::io::Result<Client>
|
||||||
where
|
where
|
||||||
T: $crate::Transport<
|
T: $crate::Transport<$crate::ClientMessage<Request>, $crate::Response<Response>> + Send + 'static,
|
||||||
Item = $crate::Response<Response>,
|
|
||||||
SinkItem = $crate::ClientMessage<Request>> + Send + 'static,
|
|
||||||
{
|
{
|
||||||
Ok(Client($crate::client::new(config, transport).await?))
|
Ok(Client($crate::client::new(config, transport).await?))
|
||||||
}
|
}
|
||||||
@@ -321,7 +319,7 @@ mod functional_test {
|
|||||||
let (tx, rx) = channel::unbounded();
|
let (tx, rx) = channel::unbounded();
|
||||||
tokio_executor::spawn(
|
tokio_executor::spawn(
|
||||||
crate::Server::default()
|
crate::Server::default()
|
||||||
.incoming(stream::once(ready(Ok(rx))))
|
.incoming(stream::once(ready(rx)))
|
||||||
.respond_with(serve(Server))
|
.respond_with(serve(Server))
|
||||||
.unit_error()
|
.unit_error()
|
||||||
.boxed()
|
.boxed()
|
||||||
@@ -350,7 +348,7 @@ mod functional_test {
|
|||||||
let (tx, rx) = channel::unbounded();
|
let (tx, rx) = channel::unbounded();
|
||||||
tokio_executor::spawn(
|
tokio_executor::spawn(
|
||||||
rpc::Server::default()
|
rpc::Server::default()
|
||||||
.incoming(stream::once(ready(Ok(rx))))
|
.incoming(stream::once(ready(rx)))
|
||||||
.respond_with(serve(Server))
|
.respond_with(serve(Server))
|
||||||
.unit_error()
|
.unit_error()
|
||||||
.boxed()
|
.boxed()
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
// 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,
|
|
||||||
integer_atomics,
|
|
||||||
async_await,
|
|
||||||
proc_macro_hygiene
|
|
||||||
)]
|
|
||||||
|
|
||||||
extern crate test;
|
|
||||||
|
|
||||||
use futures::{compat::Executor01CompatExt, future, prelude::*};
|
|
||||||
use test::stats::Stats;
|
|
||||||
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 = bincode_transport::connect(&addr).await?;
|
|
||||||
let mut client = ack::new_stub(client::Config::default(), conn).await?;
|
|
||||||
|
|
||||||
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 = client.ack(context::current()).await;
|
|
||||||
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(tokio::executor::DefaultExecutor::current().compat());
|
|
||||||
|
|
||||||
tokio::run(bench().map_err(|e| panic!(e.to_string())).boxed().compat())
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,7 @@ readme = "../README.md"
|
|||||||
description = "foundations for tracing in tarpc"
|
description = "foundations for tracing in tarpc"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand = "0.6"
|
rand = "0.7"
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ use std::{
|
|||||||
///
|
///
|
||||||
/// Consists of a span identifying an event, an optional parent span identifying a causal event
|
/// 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.
|
/// that triggered the current span, and a trace with which all related spans are associated.
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
/// An identifier of the trace associated with the current context. A trace ID is typically
|
/// An identifier of the trace associated with the current context. A trace ID is typically
|
||||||
@@ -46,12 +46,12 @@ pub struct Context {
|
|||||||
|
|
||||||
/// A 128-bit UUID identifying a trace. All spans caused by the same originating span share the
|
/// A 128-bit UUID identifying a trace. All spans caused by the same originating span share the
|
||||||
/// same trace ID.
|
/// same trace ID.
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct TraceId(u128);
|
pub struct TraceId(u128);
|
||||||
|
|
||||||
/// A 64-bit identifier of a span within a trace. The identifier is unique within the span's trace.
|
/// 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)]
|
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct SpanId(u64);
|
pub struct SpanId(u64);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user