From 637f9f28a917b01a6db1459042466a3bdb3dde66 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Wed, 10 Jan 2024 13:10:48 +0100 Subject: [PATCH 01/49] deps: Update flume This now uses the same flume version as iroh. Closes #57 --- Cargo.lock | 5 ++--- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b58f610..cd8e8fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,14 +164,13 @@ dependencies = [ [[package]] name = "flume" -version = "0.10.14" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "futures-core", "futures-sink", "nanorand", - "pin-project", "spin 0.9.8", ] diff --git a/Cargo.toml b/Cargo.toml index b27eacf..00d8a99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ rust-version = "1.65" [dependencies] bincode = { version = "1.3.3", optional = true } bytes = { version = "1", optional = true } -flume = { version = "0.10", optional = true } +flume = { version = "0.11", optional = true } futures = "0.3" hyper = { version = "0.14.16", features = ["full"], optional = true } pin-project = "1" From d3f55ced941448de4e3b571ae6bdf6bc3942d4fb Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Fri, 19 Jan 2024 09:52:35 +0100 Subject: [PATCH 02/49] chore(docs): enable all features for docs.rs builds This enables all features for doc.rs builds, this should make sure all the optional features are documented. Fixes #59 --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index a6b5309..351307c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,9 @@ combined-transport = [] macros = [] default = [] +[package.metadata.docs.rs] +all-features = true + [[example]] name = "errors" required-features = ["flume-transport"] From b25d30d6749c7508cf3e2e425703be53fd52c49e Mon Sep 17 00:00:00 2001 From: Divma <26765164+divagant-martian@users.noreply.github.com> Date: Fri, 9 Feb 2024 15:23:38 -0500 Subject: [PATCH 03/49] chore: clippy (#61) --- tests/hyper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hyper.rs b/tests/hyper.rs index 6b26cd5..249545b 100644 --- a/tests/hyper.rs +++ b/tests/hyper.rs @@ -283,7 +283,7 @@ async fn hyper_channel_errors() -> anyhow::Result<()> { // response small - should succeed let res = client.rpc(BigResponseRequest(10_000_000)).await; - assert_matches!(res, Ok(_)); + assert!(res.is_ok()); assert_server_result!(Ok(())); // response big - should fail From 865622e99c0618dca16e530b5a44fe3f1f2bdced Mon Sep 17 00:00:00 2001 From: Divma <26765164+divagant-martian@users.noreply.github.com> Date: Tue, 27 Feb 2024 04:59:31 -0500 Subject: [PATCH 04/49] fix: nightly failures (#66) --- Cargo.toml | 4 +- examples/store.rs | 2 +- tests/hyper.rs | 212 +++++++++++++++++++++++----------------------- 3 files changed, 109 insertions(+), 109 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5b0f3ce..6eb3de1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ futures = "0.3" hyper = { version = "0.14.16", features = ["full"], optional = true } pin-project = "1" quinn = { version = "0.10", optional = true } -serde = { version = "1.0.103" } +serde = { version = "1.0.183" } tokio = { version = "1", default-features = false, features = ["macros"] } tokio-serde = { version = "0.8", features = ["bincode"], optional = true } tokio-util = { version = "0.7", features = ["codec"], optional = true } @@ -35,7 +35,7 @@ tracing = "0.1" version = "0.4.20" [dev-dependencies] -anyhow = "1" +anyhow = "1.0.73" async-stream = "0.3.3" derive_more = "0.99.17" serde = { version = "1", features = ["derive"] } diff --git a/examples/store.rs b/examples/store.rs index dbbb182..19c9ab9 100644 --- a/examples/store.rs +++ b/examples/store.rs @@ -5,7 +5,7 @@ use futures::{SinkExt, Stream, StreamExt}; use quic_rpc::{ server::RpcServerError, transport::{flume, Connection, ServerEndpoint}, - Service, ServiceEndpoint, *, + *, }; use serde::{Deserialize, Serialize}; use std::{fmt::Debug, result}; diff --git a/tests/hyper.rs b/tests/hyper.rs index 249545b..ba843c1 100644 --- a/tests/hyper.rs +++ b/tests/hyper.rs @@ -31,6 +31,105 @@ fn run_server(addr: &SocketAddr) -> JoinHandle> { }) } +#[derive(Debug, Serialize, Deserialize, From, TryInto)] +enum TestResponse { + Unit(()), + Big(Vec), + NoSer(NoSer), + NoDeser(NoDeser), +} + +type SC = HyperServerEndpoint; + +/// request that can be too big +#[derive(Debug, Serialize, Deserialize)] +pub struct BigRequest(Vec); + +/// request that looks serializable but isn't +#[derive(Debug, Serialize, Deserialize)] +pub struct NoSerRequest(NoSer); + +/// request that looks deserializable but isn't +#[derive(Debug, Serialize, Deserialize)] +pub struct NoDeserRequest(NoDeser); + +/// request where the response is not serializable +#[derive(Debug, Serialize, Deserialize)] +pub struct NoSerResponseRequest; + +/// request where the response is not deserializable +#[derive(Debug, Serialize, Deserialize)] +pub struct NoDeserResponseRequest; + +/// request that can produce a response that is too big +#[derive(Debug, Serialize, Deserialize)] +pub struct BigResponseRequest(usize); + +/// helper struct that implements serde::Serialize but errors on serialization +#[derive(Debug, Deserialize)] +pub struct NoSer; + +impl serde::Serialize for NoSer { + fn serialize(&self, _serializer: S) -> Result + where + S: serde::Serializer, + { + Err(serde::ser::Error::custom("nope")) + } +} + +/// helper struct that implements serde::Deserialize but errors on deserialization +#[derive(Debug, Serialize)] +pub struct NoDeser; + +impl<'de> serde::Deserialize<'de> for NoDeser { + fn deserialize(_deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Err(serde::de::Error::custom("nope")) + } +} + +#[allow(clippy::enum_variant_names)] +#[derive(Debug, Serialize, Deserialize, From, TryInto)] +enum TestRequest { + BigRequest(BigRequest), + NoSerRequest(NoSerRequest), + NoDeserRequest(NoDeserRequest), + NoSerResponseRequest(NoSerResponseRequest), + NoDeserResponseRequest(NoDeserResponseRequest), + BigResponseRequest(BigResponseRequest), +} + +#[derive(Debug, Clone)] +struct TestService; + +impl Service for TestService { + type Req = TestRequest; + type Res = TestResponse; +} + +impl TestService { + async fn big(self, _req: BigRequest) {} + + async fn noser(self, _req: NoSerRequest) {} + + async fn nodeser(self, _req: NoDeserRequest) {} + + async fn noserresponse(self, _req: NoSerResponseRequest) -> NoSer { + NoSer + } + + async fn nodeserresponse(self, _req: NoDeserResponseRequest) -> NoDeser { + NoDeser + } + + async fn bigresponse(self, req: BigResponseRequest) -> Vec { + vec![0; req.0] + } +} + #[tokio::test] async fn hyper_channel_bench() -> anyhow::Result<()> { let addr: SocketAddr = "127.0.0.1:3000".parse()?; @@ -57,114 +156,15 @@ async fn hyper_channel_smoke() -> anyhow::Result<()> { Ok(()) } +declare_rpc!(TestService, BigRequest, ()); +declare_rpc!(TestService, NoSerRequest, ()); +declare_rpc!(TestService, NoDeserRequest, ()); +declare_rpc!(TestService, NoSerResponseRequest, NoSer); +declare_rpc!(TestService, NoDeserResponseRequest, NoDeser); +declare_rpc!(TestService, BigResponseRequest, Vec); + #[tokio::test] async fn hyper_channel_errors() -> anyhow::Result<()> { - type SC = HyperServerEndpoint; - - /// request that can be too big - #[derive(Debug, Serialize, Deserialize)] - pub struct BigRequest(Vec); - - /// request that looks serializable but isn't - #[derive(Debug, Serialize, Deserialize)] - pub struct NoSerRequest(NoSer); - - /// request that looks deserializable but isn't - #[derive(Debug, Serialize, Deserialize)] - pub struct NoDeserRequest(NoDeser); - - /// request where the response is not serializable - #[derive(Debug, Serialize, Deserialize)] - pub struct NoSerResponseRequest; - - /// request where the response is not deserializable - #[derive(Debug, Serialize, Deserialize)] - pub struct NoDeserResponseRequest; - - /// request that can produce a response that is too big - #[derive(Debug, Serialize, Deserialize)] - pub struct BigResponseRequest(usize); - - /// helper struct that implements serde::Serialize but errors on serialization - #[derive(Debug, Deserialize)] - pub struct NoSer; - - impl serde::Serialize for NoSer { - fn serialize(&self, _serializer: S) -> Result - where - S: serde::Serializer, - { - Err(serde::ser::Error::custom("nope")) - } - } - - /// helper struct that implements serde::Deserialize but errors on deserialization - #[derive(Debug, Serialize)] - pub struct NoDeser; - - impl<'de> serde::Deserialize<'de> for NoDeser { - fn deserialize(_deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Err(serde::de::Error::custom("nope")) - } - } - - #[allow(clippy::enum_variant_names)] - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - enum TestRequest { - BigRequest(BigRequest), - NoSerRequest(NoSerRequest), - NoDeserRequest(NoDeserRequest), - NoSerResponseRequest(NoSerResponseRequest), - NoDeserResponseRequest(NoDeserResponseRequest), - BigResponseRequest(BigResponseRequest), - } - - #[derive(Debug, Serialize, Deserialize, From, TryInto)] - enum TestResponse { - Unit(()), - Big(Vec), - NoSer(NoSer), - NoDeser(NoDeser), - } - - #[derive(Debug, Clone)] - struct TestService; - - impl Service for TestService { - type Req = TestRequest; - type Res = TestResponse; - } - - impl TestService { - async fn big(self, _req: BigRequest) {} - - async fn noser(self, _req: NoSerRequest) {} - - async fn nodeser(self, _req: NoDeserRequest) {} - - async fn noserresponse(self, _req: NoSerResponseRequest) -> NoSer { - NoSer - } - - async fn nodeserresponse(self, _req: NoDeserResponseRequest) -> NoDeser { - NoDeser - } - - async fn bigresponse(self, req: BigResponseRequest) -> Vec { - vec![0; req.0] - } - } - - declare_rpc!(TestService, BigRequest, ()); - declare_rpc!(TestService, NoSerRequest, ()); - declare_rpc!(TestService, NoDeserRequest, ()); - declare_rpc!(TestService, NoSerResponseRequest, NoSer); - declare_rpc!(TestService, NoDeserResponseRequest, NoDeser); - declare_rpc!(TestService, BigResponseRequest, Vec); - #[allow(clippy::type_complexity)] fn run_test_server( addr: &SocketAddr, From 3323574c972dbdf4dc4e9ae81ab8f32d27b7f3c2 Mon Sep 17 00:00:00 2001 From: Divma <26765164+divagant-martian@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:24:57 -0500 Subject: [PATCH 05/49] fix: rpc client concurrently waits for requests and new connections (#62) --- src/transport/quinn.rs | 261 +++++++++++++++++++++++++++++++++++------ tests/math.rs | 32 +++++ tests/quinn.rs | 59 +++++++++- 3 files changed, 312 insertions(+), 40 deletions(-) diff --git a/src/transport/quinn.rs b/src/transport/quinn.rs index 78d91b5..101739b 100644 --- a/src/transport/quinn.rs +++ b/src/transport/quinn.rs @@ -301,47 +301,103 @@ impl QuinnConnection { name: String, requests: flume::Receiver>>, ) { - 'outer: loop { - tracing::debug!("Connecting to {} as {}", addr, name); - let connecting = match endpoint.connect(addr, &name) { - Ok(connecting) => connecting, - Err(e) => { - tracing::warn!("error calling connect: {}", e); - // try again. Maybe delay? - continue; + let reconnect = ReconnectHandler { + endpoint, + state: ConnectionState::NotConnected, + addr, + name, + }; + futures::pin_mut!(reconnect); + + let mut receiver = Receiver::new(&requests); + + let mut pending_request: Option< + oneshot::Sender>, + > = None; + let mut connection = None; + + loop { + let mut conn_result = None; + let mut chann_result = None; + if !reconnect.connected() && pending_request.is_none() { + match futures::future::select(reconnect.as_mut(), receiver.next()).await { + futures::future::Either::Left((connection_result, _)) => { + conn_result = Some(connection_result) + } + futures::future::Either::Right((channel_result, _)) => { + chann_result = Some(channel_result); + } } - }; - let connection = match connecting.await { - Ok(connection) => connection, - Err(e) => { - tracing::warn!("error awaiting connect: {}", e); - // try again. Maybe delay? - continue; + } else if !reconnect.connected() { + // only need a new connection + conn_result = Some(reconnect.as_mut().await); + } else if pending_request.is_none() { + // there is a connection, just need a request + chann_result = Some(receiver.next().await); + } + + if let Some(conn_result) = conn_result { + tracing::trace!("tick: connection result"); + match conn_result { + Ok(new_connection) => { + connection = Some(new_connection); + } + Err(e) => { + let connection_err = match e { + ReconnectErr::Connect(e) => { + // TODO(@divma): the type for now accepts only a + // ConnectionError, not a ConnectError. I'm mapping this now to + // some ConnectionError since before it was not even reported. + // Maybe adjust the type? + tracing::warn!(%e, "error calling connect"); + quinn::ConnectionError::Reset + } + ReconnectErr::Connection(e) => { + tracing::warn!(%e, "failed to connect"); + e + } + }; + if let Some(request) = pending_request.take() { + if request.send(Err(connection_err)).is_err() { + tracing::debug!("requester dropped"); + } + } + } } - }; - loop { - tracing::debug!("Awaiting request for new bidi substream..."); - let request = match requests.recv_async().await { - Ok(request) => request, - Err(_) => { + } + + if let Some(req) = chann_result { + tracing::trace!("tick: bidi request"); + match req { + Some(request) => pending_request = Some(request), + None => { tracing::debug!("client dropped"); - connection.close(0u32.into(), b"requester dropped"); + if let Some(connection) = connection { + connection.close(0u32.into(), b"requester dropped"); + } break; } - }; - tracing::debug!("Got request for new bidi substream"); - match connection.open_bi().await { - Ok(pair) => { - tracing::debug!("Bidi substream opened"); - if request.send(Ok(pair)).is_err() { - tracing::debug!("requester dropped"); + } + } + + if let Some(connection) = connection.as_mut() { + if let Some(request) = pending_request.take() { + match connection.open_bi().await { + Ok(pair) => { + tracing::debug!("Bidi substream opened"); + if request.send(Ok(pair)).is_err() { + tracing::debug!("requester dropped"); + } + } + Err(e) => { + tracing::warn!("error opening bidi substream: {}", e); + tracing::warn!("recreating connection"); + // NOTE: the connection might be stale, so we recreate the + // connection and set the request as pending instead of + // sending the error as a response + reconnect.set_not_connected(); + pending_request = Some(request); } - } - Err(e) => { - tracing::warn!("error opening bidi substream: {}", e); - tracing::warn!("recreating connection"); - // try again. Maybe delay? - continue 'outer; } } } @@ -392,6 +448,141 @@ impl QuinnConnection { } } +struct ReconnectHandler { + endpoint: quinn::Endpoint, + state: ConnectionState, + addr: SocketAddr, + name: String, +} + +impl ReconnectHandler { + pub fn set_not_connected(&mut self) { + self.state.set_not_connected() + } + + pub fn connected(&self) -> bool { + matches!(self.state, ConnectionState::Connected(_)) + } +} + +enum ConnectionState { + /// There is no active connection. An attempt to connect will be made. + NotConnected, + /// Connecting to the remote. + Connecting(quinn::Connecting), + /// A connection is already established. In this state, no more connection attempts are made. + Connected(quinn::Connection), + /// Intermediate state while processing. + Poisoned, +} + +impl ConnectionState { + pub fn poison(&mut self) -> ConnectionState { + std::mem::replace(self, ConnectionState::Poisoned) + } + + pub fn set_not_connected(&mut self) { + *self = ConnectionState::NotConnected + } +} + +enum ReconnectErr { + Connect(quinn::ConnectError), + Connection(quinn::ConnectionError), +} + +impl Future for ReconnectHandler { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.state.poison() { + ConnectionState::NotConnected => match self.endpoint.connect(self.addr, &self.name) { + Ok(connecting) => { + self.state = ConnectionState::Connecting(connecting); + self.poll(cx) + } + Err(e) => { + self.state = ConnectionState::NotConnected; + Poll::Ready(Err(ReconnectErr::Connect(e))) + } + }, + ConnectionState::Connecting(mut connecting) => match connecting.poll_unpin(cx) { + Poll::Ready(res) => match res { + Ok(connection) => { + self.state = ConnectionState::Connected(connection.clone()); + Poll::Ready(Ok(connection)) + } + Err(e) => { + self.state = ConnectionState::NotConnected; + Poll::Ready(Err(ReconnectErr::Connection(e))) + } + }, + Poll::Pending => { + self.state = ConnectionState::Connecting(connecting); + Poll::Pending + } + }, + ConnectionState::Connected(connection) => { + self.state = ConnectionState::Connected(connection.clone()); + Poll::Ready(Ok(connection)) + } + ConnectionState::Poisoned => unreachable!("poisoned connection state"), + } + } +} + +/// Wrapper over [`flume::Receiver`] that can be used with [`tokio::select`]. +/// +/// NOTE: from https://github.com/zesterer/flume/issues/104: +/// > If RecvFut is dropped without being polled, the item is never received. +enum Receiver<'a, T> +where + Self: 'a, +{ + PreReceive(&'a flume::Receiver), + Receiving(&'a flume::Receiver, flume::r#async::RecvFut<'a, T>), + Poisoned, +} + +impl<'a, T> Receiver<'a, T> { + fn new(recv: &'a flume::Receiver) -> Self { + Receiver::PreReceive(recv) + } + + fn poison(&mut self) -> Self { + std::mem::replace(self, Self::Poisoned) + } +} + +impl<'a, T> futures::stream::Stream for Receiver<'a, T> { + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.poison() { + Receiver::PreReceive(recv) => { + let fut = recv.recv_async(); + *self = Receiver::Receiving(recv, fut); + self.poll_next(cx) + } + Receiver::Receiving(recv, mut fut) => match fut.poll_unpin(cx) { + Poll::Ready(Ok(t)) => { + *self = Receiver::PreReceive(recv); + Poll::Ready(Some(t)) + } + Poll::Ready(Err(flume::RecvError::Disconnected)) => { + *self = Receiver::PreReceive(recv); + Poll::Ready(None) + } + Poll::Pending => { + *self = Receiver::Receiving(recv, fut); + Poll::Pending + } + }, + Receiver::Poisoned => unreachable!("poisoned receiver state"), + } + } +} + impl fmt::Debug for QuinnConnection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ClientChannel") diff --git a/tests/math.rs b/tests/math.rs index ab44b75..583b7ca 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -153,6 +153,38 @@ impl ComputeService { } } + /// Runs the service until `count` requests have been received. + pub async fn server_bounded>( + server: RpcServer, + count: usize, + ) -> result::Result, RpcServerError> { + tracing::info!(%count, "server running"); + let s = server; + let mut received = 0; + let service = ComputeService; + while received < count { + received += 1; + let (req, chan) = s.accept().await?; + let service = service.clone(); + tokio::spawn(async move { + use ComputeRequest::*; + tracing::info!(?req, "got request"); + #[rustfmt::skip] + match req { + Sqr(msg) => chan.rpc(msg, service, ComputeService::sqr).await, + Sum(msg) => chan.client_streaming(msg, service, ComputeService::sum).await, + Fibonacci(msg) => chan.server_streaming(msg, service, ComputeService::fibonacci).await, + Multiply(msg) => chan.bidi_streaming(msg, service, ComputeService::multiply).await, + SumUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, + MultiplyUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, + }?; + Ok::<_, RpcServerError>(()) + }); + } + tracing::info!(%count, "server finished"); + Ok(s) + } + pub async fn server_par>( server: RpcServer, parallelism: usize, diff --git a/tests/quinn.rs b/tests/quinn.rs index a5a0126..de5fc1f 100644 --- a/tests/quinn.rs +++ b/tests/quinn.rs @@ -4,7 +4,7 @@ use std::{ sync::Arc, }; -use quic_rpc::{RpcClient, RpcServer}; +use quic_rpc::{transport, RpcClient, RpcServer}; use quinn::{ClientConfig, Endpoint, ServerConfig}; use tokio::task::JoinHandle; @@ -91,7 +91,7 @@ pub fn make_endpoints(port: u16) -> anyhow::Result { fn run_server(server: quinn::Endpoint) -> JoinHandle> { tokio::task::spawn(async move { - let connection = quic_rpc::transport::quinn::QuinnServerEndpoint::new(server)?; + let connection = transport::quinn::QuinnServerEndpoint::new(server)?; let server = RpcServer::::new(connection); ComputeService::server(server).await?; anyhow::Ok(()) @@ -110,8 +110,7 @@ async fn quinn_channel_bench() -> anyhow::Result<()> { tracing::debug!("Starting server"); let server_handle = run_server(server); tracing::debug!("Starting client"); - let client = - quic_rpc::transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); + let client = transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); let client = RpcClient::::new(client); tracing::debug!("Starting benchmark"); bench(client, 50000).await?; @@ -129,8 +128,58 @@ async fn quinn_channel_smoke() -> anyhow::Result<()> { } = make_endpoints(12346)?; let server_handle = run_server(server); let client_connection = - quic_rpc::transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); + transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); smoke_test(client_connection).await?; server_handle.abort(); Ok(()) } + +/// Test that using the client after the server goes away and comes back behaves as if the server +/// had never gone away in the first place. +/// +/// This is a regression test. +#[tokio::test] +async fn server_away_and_back() -> anyhow::Result<()> { + tracing_subscriber::fmt::try_init().ok(); + tracing::info!("Creating endpoints"); + + let server_addr: SocketAddr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 12347)); + let (server_config, server_cert) = configure_server()?; + + // create the RPC client + let client = make_client_endpoint("0.0.0.0:0".parse()?, &[&server_cert])?; + let client_connection = + transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); + let client = RpcClient::new(client_connection); + + // send a request. No server available so it should fail + client.rpc(Sqr(4)).await.unwrap_err(); + + // create the RPC Server + let server = Endpoint::server(server_config.clone(), server_addr)?; + let connection = transport::quinn::QuinnServerEndpoint::new(server)?; + let server = RpcServer::::new(connection); + let server_handle = tokio::task::spawn(ComputeService::server_bounded(server, 1)); + + // send the first request and wait for the response to ensure everything works as expected + let SqrResponse(response) = client.rpc(Sqr(4)).await.unwrap(); + assert_eq!(response, 16); + + let server = server_handle.await.unwrap().unwrap(); + drop(server); + // wait for drop to free the socket + tokio::time::sleep(tokio::time::Duration::from_millis(300)).await; + + // make the server run again + let server = Endpoint::server(server_config, server_addr)?; + let connection = transport::quinn::QuinnServerEndpoint::new(server)?; + let server = RpcServer::::new(connection); + let server_handle = tokio::task::spawn(ComputeService::server_bounded(server, 5)); + + // server is running, this should work + let SqrResponse(response) = client.rpc(Sqr(3)).await.unwrap(); + assert_eq!(response, 9); + + server_handle.abort(); + Ok(()) +} From 2a5a005f0c673e5000b2c449fe8180112866a202 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 27 Feb 2024 18:22:37 +0200 Subject: [PATCH 06/49] increase version number --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97f16e0..ab64180 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -701,7 +701,7 @@ dependencies = [ [[package]] name = "quic-rpc" -version = "0.6.1" +version = "0.6.2" dependencies = [ "anyhow", "async-stream", diff --git a/Cargo.toml b/Cargo.toml index 6eb3de1..cccdc6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quic-rpc" -version = "0.6.1" +version = "0.6.2" edition = "2021" authors = ["Rüdiger Klaehn "] keywords = ["api", "protocol", "network", "rpc"] From d9b385ce8ba66430c6a2744c0f595a74a9e3d578 Mon Sep 17 00:00:00 2001 From: Friedel Ziegelmayer Date: Tue, 27 Feb 2024 19:15:23 +0100 Subject: [PATCH 07/49] fix(ci): cancel stale repeat jobs (#64) Co-authored-by: Asmir Avdicevic Co-authored-by: Divma <26765164+divagant-martian@users.noreply.github.com> --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c26bfb5..5bcb304 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,11 @@ on: branches: - main + +concurrency: + group: tests-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + env: MSRV: "1.63" RUST_BACKTRACE: 1 From 17824114e5ddd50c3bfd590e884e977f401e6e0b Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Mon, 4 Mar 2024 22:59:47 +0100 Subject: [PATCH 08/49] wip: modularize example --- Cargo.toml | 6 +- examples/modularize.rs | 304 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 examples/modularize.rs diff --git a/Cargo.toml b/Cargo.toml index cccdc6a..0af378c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ quinn-transport = ["flume", "quinn", "bincode", "tokio-serde", "tokio-util"] flume-transport = ["flume"] combined-transport = [] macros = [] -default = [] +default = ["flume-transport"] [[example]] name = "errors" @@ -68,6 +68,10 @@ required-features = ["flume-transport", "macros"] name = "store" required-features = ["flume-transport"] +[[example]] +name = "modularize" +required-features = ["flume-transport"] + [workspace] members = ["examples/split/types", "examples/split/server", "examples/split/client"] diff --git a/examples/modularize.rs b/examples/modularize.rs new file mode 100644 index 0000000..181d3ec --- /dev/null +++ b/examples/modularize.rs @@ -0,0 +1,304 @@ +use quic_rpc::{client::RpcClient, transport::flume, RpcServer, ServiceEndpoint}; +use tracing::warn; + +async fn run_server( + server_ep: impl ServiceEndpoint, + handler: app::Handler, +) -> anyhow::Result<()> { + let server = RpcServer::new(server_ep); + loop { + let (req, chan) = server.accept().await?; + let handler = handler.clone(); + tokio::task::spawn(async move { + handler.handle(req, chan).await; + }); + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let (server_conn, client_conn) = flume::connection::(1); + let server_handle = tokio::task::spawn(async move { + let handler = app::Handler::default(); + if let Err(err) = run_server(server_conn, handler).await { + warn!(?err, "RPC server failed"); + }; + }); + + app::client_demo(client_conn).await; + server_handle.await?; + Ok(()) +} + +mod app { + use super::{calc, clock}; + use derive_more::{From, TryInto}; + use quic_rpc::{server::RpcChannel, ServiceConnection, ServiceEndpoint}; + use serde::{Deserialize, Serialize}; + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Request { + Calc(calc::Request), + Clock(clock::Request), + } + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Response { + Calc(calc::Response), + Clock(clock::Response), + } + + #[derive(Copy, Clone, Debug)] + pub struct Service; + impl quic_rpc::Service for Service { + type Req = Request; + type Res = Response; + } + + #[derive(Clone, Default)] + pub struct Handler { + calc: calc::Handler, + clock: clock::Handler, + } + + impl Handler { + pub async fn handle>( + &self, + req: Request, + chan: RpcChannel, + ) { + let handler = self.clone(); + // let _res = match req { + // Request::Calc(req) => self.calc.handle(req, chan).await, + // Request::Clock(req) => self.clock.handle(req, chan).await, + // }; + } + } + + pub async fn client_demo>(conn: C) { + // clock::client_demo(conn).await; + // calc::client_demo(conn).await; + + // let client = quic_rpc::RpcClient::::new(conn); + // let res = client.rpc(calc::AddRequest(2, 7)).await; + // println!("rpc res: {res:?}"); + } + + impl From for Request { + fn from(value: calc::AddRequest) -> Self { + Request::Calc(calc::Request::Add(value)) + } + } +} + +mod calc { + use derive_more::{From, TryInto}; + use quic_rpc::{message::RpcMsg, server::RpcChannel, ServiceConnection, ServiceEndpoint}; + use serde::{Deserialize, Serialize}; + use std::fmt::Debug; + + #[derive(Debug, Serialize, Deserialize)] + pub struct AddRequest(pub i64, pub i64); + + impl RpcMsg for AddRequest { + type Response = AddResponse; + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct AddResponse(pub i64); + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Request { + Add(AddRequest), + } + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Response { + Add(AddResponse), + } + + #[derive(Copy, Clone, Debug)] + pub struct Service; + impl quic_rpc::Service for Service { + type Req = Request; + type Res = Response; + } + + #[derive(Clone, Default)] + pub struct Handler; + + impl Handler { + pub async fn handle>( + &self, + req: Request, + chan: RpcChannel, + ) { + let handler = self.clone(); + let _res = match req { + Request::Add(req) => chan.rpc(req, handler, Self::on_add).await, + }; + } + + pub async fn on_add(self, req: AddRequest) -> AddResponse { + AddResponse(req.0 + req.1) + } + } + + pub async fn client_demo>(conn: C) { + let client = quic_rpc::RpcClient::::new(conn); + + // a rpc call + for i in 0..3 { + println!("a rpc call [{i}]"); + let client = client.clone(); + tokio::task::spawn(async move { + let res = client.rpc(AddRequest(2, i)).await; + println!("rpc res [{i}]: {res:?}"); + }); + } + } +} + +mod clock { + use anyhow::Result; + use derive_more::{From, TryInto}; + use futures::{Stream, StreamExt}; + use quic_rpc::{ + message::{Msg, ServerStreaming, ServerStreamingMsg}, + server::RpcChannel, + RpcClient, ServiceConnection, ServiceEndpoint, + }; + use serde::{Deserialize, Serialize}; + use std::{ + fmt::Debug, + sync::{Arc, RwLock}, + time::Duration, + }; + use tokio::sync::Notify; + + #[derive(Debug, Serialize, Deserialize)] + pub struct TickRequest; + + impl Msg for TickRequest { + type Pattern = ServerStreaming; + } + + impl ServerStreamingMsg for TickRequest { + type Response = TickResponse; + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct TickResponse { + tick: usize, + } + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Request { + Tick(TickRequest), + } + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Response { + Tick(TickResponse), + } + + #[derive(Copy, Clone, Debug)] + pub struct Service; + impl quic_rpc::Service for Service { + type Req = Request; + type Res = Response; + } + + #[derive(Clone)] + pub struct Handler { + tick: Arc>, + ontick: Arc, + } + + impl Default for Handler { + fn default() -> Self { + Self::new(Duration::from_secs(1)) + } + } + + impl Handler { + pub fn new(tick_duration: Duration) -> Self { + let h = Handler { + tick: Default::default(), + ontick: Default::default(), + }; + let h2 = h.clone(); + tokio::task::spawn(async move { + loop { + tokio::time::sleep(tick_duration).await; + *h2.tick.write().unwrap() += 1; + h2.ontick.notify_waiters(); + } + }); + h + } + + pub async fn handle>( + &self, + req: Request, + chan: RpcChannel, + ) { + let handler = self.clone(); + let _res = match req { + Request::Tick(req) => chan.server_streaming(req, handler, Self::on_tick).await, + }; + } + + pub fn on_tick( + self, + req: TickRequest, + ) -> impl Stream + Send + 'static { + let (tx, rx) = flume::bounded(2); + tokio::task::spawn(async move { + if let Err(err) = self.on_tick0(req, tx).await { + tracing::warn!(?err, "on_tick RPC handler failed"); + } + }); + rx.into_stream() + } + + pub async fn on_tick0( + self, + _req: TickRequest, + tx: flume::Sender, + ) -> Result<()> { + loop { + let tick = *self.tick.read().unwrap(); + tx.send_async(TickResponse { tick }).await?; + self.ontick.notified().await; + } + } + } + + pub async fn client_demo>(conn: C) { + let client = RpcClient::::new(conn); + + // a rpc call + for i in 0..3 { + println!("a rpc call [{i}]"); + let client = client.clone(); + tokio::task::spawn(async move { + if let Err(err) = run_tick(client, i).await { + tracing::warn!(?err, "client: run_tick failed"); + } + }); + } + } + + async fn run_tick>( + client: RpcClient, + id: usize, + ) -> anyhow::Result<()> { + let mut res = client.server_streaming(TickRequest).await?; + while let Some(res) = res.next().await { + println!("stream [{id}]: {res:?}"); + } + println!("stream [{id}]: done"); + Ok(()) + } +} From 58b029ed2022040e0eb924d80884f521ed76c195 Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Tue, 5 Mar 2024 01:10:45 +0100 Subject: [PATCH 09/49] refactor: add mapped methods for client and server --- examples/modularize.rs | 115 ++++++++++++++++++------------ src/client.rs | 155 ++++++++++++++++++++++++++++++++++++----- src/server.rs | 114 ++++++++++++++++++++++++++---- 3 files changed, 310 insertions(+), 74 deletions(-) diff --git a/examples/modularize.rs b/examples/modularize.rs index 181d3ec..13aa1ef 100644 --- a/examples/modularize.rs +++ b/examples/modularize.rs @@ -1,4 +1,4 @@ -use quic_rpc::{client::RpcClient, transport::flume, RpcServer, ServiceEndpoint}; +use quic_rpc::{transport::flume, RpcServer, ServiceEndpoint}; use tracing::warn; async fn run_server( @@ -18,20 +18,21 @@ async fn run_server( #[tokio::main] async fn main() -> anyhow::Result<()> { let (server_conn, client_conn) = flume::connection::(1); + let handler = app::Handler::default(); let server_handle = tokio::task::spawn(async move { - let handler = app::Handler::default(); if let Err(err) = run_server(server_conn, handler).await { warn!(?err, "RPC server failed"); }; }); - app::client_demo(client_conn).await; + app::client_demo(client_conn).await?; server_handle.await?; Ok(()) } mod app { use super::{calc, clock}; + use anyhow::Result; use derive_more::{From, TryInto}; use quic_rpc::{server::RpcChannel, ServiceConnection, ServiceEndpoint}; use serde::{Deserialize, Serialize}; @@ -67,33 +68,30 @@ mod app { req: Request, chan: RpcChannel, ) { - let handler = self.clone(); - // let _res = match req { - // Request::Calc(req) => self.calc.handle(req, chan).await, - // Request::Clock(req) => self.clock.handle(req, chan).await, - // }; + let _res = match req { + Request::Calc(req) => self.calc.handle(req, chan).await, + Request::Clock(req) => self.clock.handle(req, chan).await, + }; } } - pub async fn client_demo>(conn: C) { - // clock::client_demo(conn).await; - // calc::client_demo(conn).await; - - // let client = quic_rpc::RpcClient::::new(conn); - // let res = client.rpc(calc::AddRequest(2, 7)).await; - // println!("rpc res: {res:?}"); - } - - impl From for Request { - fn from(value: calc::AddRequest) -> Self { - Request::Calc(calc::Request::Add(value)) - } + pub async fn client_demo>(conn: C) -> Result<()> { + let client = quic_rpc::RpcClient::::new(conn); + // examples for generic handlers + calc::run_client(client.clone()).await; + clock::run_client(client.clone()).await; + // example for using module structs directly + let res = client.rpc_mapped(calc::AddRequest(21, 21)).await?; + println!("rpc res: {res:?}"); + Ok(()) } } mod calc { use derive_more::{From, TryInto}; - use quic_rpc::{message::RpcMsg, server::RpcChannel, ServiceConnection, ServiceEndpoint}; + use quic_rpc::{ + message::RpcMsg, server::RpcChannel, RpcClient, ServiceConnection, ServiceEndpoint, + }; use serde::{Deserialize, Serialize}; use std::fmt::Debug; @@ -128,14 +126,16 @@ mod calc { pub struct Handler; impl Handler { - pub async fn handle>( - &self, - req: Request, - chan: RpcChannel, - ) { + pub async fn handle(&self, req: Request, chan: RpcChannel) + where + S0: quic_rpc::Service, + S0::Req: From + Send + 'static, + S0::Res: From + Send + 'static, + E: ServiceEndpoint, + { let handler = self.clone(); let _res = match req { - Request::Add(req) => chan.rpc(req, handler, Self::on_add).await, + Request::Add(req) => chan.rpc_mapped(req, handler, Self::on_add).await, }; } @@ -144,19 +144,28 @@ mod calc { } } - pub async fn client_demo>(conn: C) { - let client = quic_rpc::RpcClient::::new(conn); - - // a rpc call + pub async fn run_client(client: RpcClient) + where + C: ServiceConnection, + S0: quic_rpc::Service, + Request: TryFrom + Into, + Response: TryFrom + Into, + { for i in 0..3 { println!("a rpc call [{i}]"); let client = client.clone(); tokio::task::spawn(async move { - let res = client.rpc(AddRequest(2, i)).await; + let res = client.rpc_mapped(AddRequest(2, i)).await; println!("rpc res [{i}]: {res:?}"); }); } } + + #[allow(unused)] + pub async fn client_demo>(conn: C) { + let client = quic_rpc::RpcClient::::new(conn); + run_client(client).await; + } } mod clock { @@ -238,14 +247,19 @@ mod clock { h } - pub async fn handle>( - &self, - req: Request, - chan: RpcChannel, - ) { + pub async fn handle(&self, req: Request, chan: RpcChannel) + where + S0: quic_rpc::Service, + S0::Req: From + Send + 'static, + S0::Res: From + Send + 'static, + E: ServiceEndpoint, + { let handler = self.clone(); let _res = match req { - Request::Tick(req) => chan.server_streaming(req, handler, Self::on_tick).await, + Request::Tick(req) => { + chan.server_streaming_mapped(req, handler, Self::on_tick) + .await + } }; } @@ -275,9 +289,19 @@ mod clock { } } + #[allow(unused)] pub async fn client_demo>(conn: C) { let client = RpcClient::::new(conn); + run_client(client).await + } + pub async fn run_client(client: RpcClient) + where + C: ServiceConnection, + S0: quic_rpc::Service, + Request: TryFrom + Into, + Response: TryFrom + Into, + { // a rpc call for i in 0..3 { println!("a rpc call [{i}]"); @@ -290,11 +314,14 @@ mod clock { } } - async fn run_tick>( - client: RpcClient, - id: usize, - ) -> anyhow::Result<()> { - let mut res = client.server_streaming(TickRequest).await?; + async fn run_tick(client: RpcClient, id: usize) -> anyhow::Result<()> + where + C: ServiceConnection, + S0: quic_rpc::Service, + Request: TryFrom + Into, + Response: TryFrom + Into, + { + let mut res = client.server_streaming_mapped(TickRequest).await?; while let Some(res) = res.next().await { println!("stream [{id}]: {res:?}"); } diff --git a/src/client.rs b/src/client.rs index a5fb86f..0250d5f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -68,6 +68,47 @@ impl, T: Into> Sink for UpdateSin } } +/// Sink that can be used to send updates to the server for the two interaction patterns +/// that support it, [crate::message::ClientStreaming] and [crate::message::BidiStreaming]. +#[pin_project] +#[derive(Debug)] +pub struct MappedUpdateSink(#[pin] C::SendSink, PhantomData, PhantomData) +where + S2: Service, + S: Service, + C: ServiceConnection, + T: Into, + S2::Req: Into; + +impl Sink for MappedUpdateSink +where + S2: Service, + S: Service, + C: ServiceConnection, + T: Into, + S2::Req: Into, +{ + type Error = C::SendError; + + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().0.poll_ready_unpin(cx) + } + + fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { + let req: S2::Req = item.into(); + let req: S::Req = req.into(); + self.project().0.start_send_unpin(req) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().0.poll_flush_unpin(cx) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().0.poll_close_unpin(cx) + } +} + impl> RpcClient { /// Create a new rpc client for a specific [Service] given a compatible /// [ServiceConnection]. @@ -86,11 +127,15 @@ impl> RpcClient { } /// RPC call to the server, single request, single response - pub async fn rpc(&self, msg: M) -> result::Result> + pub async fn rpc_mapped(&self, msg: M) -> result::Result> where - M: RpcMsg, + M: RpcMsg, + S2: Service, + S2::Req: Into + TryFrom + Send + 'static, + S2::Res: Into + TryFrom + Send + 'static, { - let msg = msg.into(); + let msg: ::Req = msg.into(); + let msg: ::Req = msg.into(); let (mut send, mut recv) = self.source.open_bi().await.map_err(RpcClientError::Open)?; send.send(msg).await.map_err(RpcClientError::::Send)?; let res = recv @@ -100,11 +145,20 @@ impl> RpcClient { .map_err(RpcClientError::::RecvError)?; // keep send alive until we have the answer drop(send); + let res = S2::Res::try_from(res).map_err(|_| RpcClientError::DowncastError)?; M::Response::try_from(res).map_err(|_| RpcClientError::DowncastError) } + /// RPC call to the server, single request, single response + pub async fn rpc(&self, msg: M) -> result::Result> + where + M: RpcMsg, + { + self.rpc_mapped::(msg).await + } + /// Bidi call to the server, request opens a stream, response is a stream - pub async fn server_streaming( + pub async fn server_streaming_mapped( &self, msg: M, ) -> result::Result< @@ -112,9 +166,13 @@ impl> RpcClient { StreamingResponseError, > where - M: ServerStreamingMsg, + M: ServerStreamingMsg, + S2: Service, + S2::Req: Into + TryFrom + Send + 'static, + S2::Res: Into + TryFrom + Send + 'static, { - let msg = msg.into(); + let msg: S2::Req = msg.into(); + let msg: S::Req = msg.into(); let (mut send, recv) = self .source .open_bi() @@ -125,6 +183,8 @@ impl> RpcClient { .await?; let recv = recv.map(move |x| match x { Ok(x) => { + let x = + S2::Res::try_from(x).map_err(|_| StreamingResponseItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| StreamingResponseItemError::DowncastError) } Err(e) => Err(StreamingResponseItemError::RecvError(e)), @@ -134,28 +194,46 @@ impl> RpcClient { Ok(recv) } + /// Bidi call to the server, request opens a stream, response is a stream + pub async fn server_streaming( + &self, + msg: M, + ) -> result::Result< + BoxStream<'static, result::Result>>, + StreamingResponseError, + > + where + M: ServerStreamingMsg, + { + self.server_streaming_mapped::(msg).await + } + /// Call to the server that allows the client to stream, single response - pub async fn client_streaming( + pub async fn client_streaming_mapped( &self, msg: M, ) -> result::Result< ( - UpdateSink, + MappedUpdateSink, BoxFuture<'static, result::Result>>, ), ClientStreamingError, > where - M: ClientStreamingMsg, + M: ClientStreamingMsg, + S2: Service, + S2::Req: Into + TryFrom + Send + 'static, + S2::Res: Into + TryFrom + Send + 'static, { - let msg = msg.into(); + let msg: S2::Req = msg.into(); + let msg: S::Req = msg.into(); let (mut send, mut recv) = self .source .open_bi() .await .map_err(ClientStreamingError::Open)?; send.send(msg).map_err(ClientStreamingError::Send).await?; - let send = UpdateSink::(send, PhantomData); + let send = MappedUpdateSink::(send, PhantomData, PhantomData); let recv = async move { let item = recv .next() @@ -164,6 +242,8 @@ impl> RpcClient { match item { Ok(x) => { + let x = S2::Res::try_from(x) + .map_err(|_| ClientStreamingItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| ClientStreamingItemError::DowncastError) } Err(e) => Err(ClientStreamingItemError::RecvError(e)), @@ -173,32 +253,73 @@ impl> RpcClient { Ok((send, recv)) } + /// Call to the server that allows the client to stream, single response + pub async fn client_streaming( + &self, + msg: M, + ) -> result::Result< + ( + MappedUpdateSink, + BoxFuture<'static, result::Result>>, + ), + ClientStreamingError, + > + where + M: ClientStreamingMsg, + { + self.client_streaming_mapped::(msg).await + } + /// Bidi call to the server, request opens a stream, response is a stream - pub async fn bidi( + pub async fn bidi_mapped( &self, msg: M, ) -> result::Result< ( - UpdateSink, + MappedUpdateSink, BoxStream<'static, result::Result>>, ), BidiError, > where - M: BidiStreamingMsg, + M: BidiStreamingMsg, + S2: Service, + S2::Req: Into + TryFrom + Send + 'static, + S2::Res: Into + TryFrom + Send + 'static, { - let msg = msg.into(); + let msg: S2::Req = msg.into(); + let msg: S::Req = msg.into(); let (mut send, recv) = self.source.open_bi().await.map_err(BidiError::Open)?; send.send(msg).await.map_err(BidiError::::Send)?; - let send = UpdateSink(send, PhantomData); + let send = MappedUpdateSink(send, PhantomData, PhantomData); let recv = recv .map(|x| match x { - Ok(x) => M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError), + Ok(x) => { + let x = S2::Res::try_from(x).map_err(|_| BidiItemError::DowncastError)?; + M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError) + } Err(e) => Err(BidiItemError::RecvError(e)), }) .boxed(); Ok((send, recv)) } + + /// Bidi call to the server, request opens a stream, response is a stream + pub async fn bidi( + &self, + msg: M, + ) -> result::Result< + ( + MappedUpdateSink, + BoxStream<'static, result::Result>>, + ), + BidiError, + > + where + M: BidiStreamingMsg, + { + self.bidi_mapped::(msg).await + } } impl> AsRef for RpcClient { diff --git a/src/server.rs b/src/server.rs index 755e945..0405c2a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -76,17 +76,20 @@ impl> RpcChannel { /// handle the message of type `M` using the given function on the target object /// /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn rpc( + pub async fn rpc_mapped( self, req: M, target: T, f: F, ) -> result::Result<(), RpcServerError> where - M: RpcMsg, + M: RpcMsg, F: FnOnce(T, M) -> Fut, Fut: Future, T: Send + 'static, + S2: Service, + S2::Req: Into + Send + 'static, + S2::Res: Into + Send + 'static, { let Self { mut send, mut recv, .. @@ -100,6 +103,7 @@ impl> RpcChannel { // get the response let res = f(target, req).await; // turn into a S::Res so we can send it + let res: S2::Res = res.into(); let res: S::Res = res.into(); // send it and return the error if any send.send(res).await.map_err(RpcServerError::SendError) @@ -107,20 +111,41 @@ impl> RpcChannel { .await } + /// handle the message of type `M` using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + pub async fn rpc( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: RpcMsg, + F: FnOnce(T, M) -> Fut, + Fut: Future, + T: Send + 'static, + { + self.rpc_mapped::(req, target, f).await + } + /// handle the message M using the given function on the target object /// /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn client_streaming( + pub async fn client_streaming_mapped( self, req: M, target: T, f: F, ) -> result::Result<(), RpcServerError> where - M: ClientStreamingMsg, + M: ClientStreamingMsg, F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, Fut: Future + Send + 'static, T: Send + 'static, + S2: Service, + S2::Req: Into + Send + 'static, + S2::Res: Into + Send + 'static, { let Self { mut send, recv, .. } = self; let (updates, read_error) = UpdateStream::new(recv); @@ -128,6 +153,7 @@ impl> RpcChannel { // get the response let res = f(target, req, updates).await; // turn into a S::Res so we can send it + let res: S2::Res = res.into(); let res: S::Res = res.into(); // send it and return the error if any send.send(res).await.map_err(RpcServerError::SendError) @@ -138,17 +164,39 @@ impl> RpcChannel { /// handle the message M using the given function on the target object /// /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn bidi_streaming( + pub async fn client_streaming( self, req: M, target: T, f: F, ) -> result::Result<(), RpcServerError> where - M: BidiStreamingMsg, + M: ClientStreamingMsg, + F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, + Fut: Future + Send + 'static, + T: Send + 'static, + { + self.client_streaming_mapped::(req, target, f) + .await + } + + /// handle the message M using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + pub async fn bidi_streaming_mapped( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: BidiStreamingMsg, F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, Str: Stream + Send + 'static, T: Send + 'static, + S2: Service, + S2::Req: Into + Send + 'static, + S2::Res: Into + Send + 'static, { let Self { mut send, recv, .. } = self; // downcast the updates @@ -157,13 +205,12 @@ impl> RpcChannel { let responses = f(target, req, updates); race2(read_error.map(Err), async move { tokio::pin!(responses); - while let Some(response) = responses.next().await { + while let Some(res) = responses.next().await { // turn into a S::Res so we can send it - let response: S::Res = response.into(); + let res: S2::Res = res.into(); + let res: S::Res = res.into(); // send it and return the error if any - send.send(response) - .await - .map_err(RpcServerError::SendError)?; + send.send(res).await.map_err(RpcServerError::SendError)?; } Ok(()) }) @@ -173,17 +220,38 @@ impl> RpcChannel { /// handle the message M using the given function on the target object /// /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn server_streaming( + pub async fn bidi_streaming( self, req: M, target: T, f: F, ) -> result::Result<(), RpcServerError> where - M: ServerStreamingMsg, + M: BidiStreamingMsg, + F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, + Str: Stream + Send + 'static, + T: Send + 'static, + { + self.bidi_streaming_mapped::(req, target, f).await + } + + /// handle the message M using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + pub async fn server_streaming_mapped( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: ServerStreamingMsg, F: FnOnce(T, M) -> Str + Send + 'static, Str: Stream + Send + 'static, T: Send + 'static, + S2: Service, + S2::Req: Into + Send + 'static, + S2::Res: Into + Send + 'static, { let Self { mut send, mut recv, .. @@ -199,6 +267,7 @@ impl> RpcChannel { tokio::pin!(responses); while let Some(response) = responses.next().await { // turn into a S::Res so we can send it + let response: S2::Res = response.into(); let response: S::Res = response.into(); // send it and return the error if any send.send(response) @@ -210,6 +279,25 @@ impl> RpcChannel { .await } + /// handle the message M using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + pub async fn server_streaming( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: ServerStreamingMsg, + F: FnOnce(T, M) -> Str + Send + 'static, + Str: Stream + Send + 'static, + T: Send + 'static, + { + self.server_streaming_mapped::(req, target, f) + .await + } + /// A rpc call that also maps the error from the user type to the wire type /// /// This is useful if you want to write your function with a convenient error type like anyhow::Error, From b41c76ae2948341a37d97a2296fb4b9dc421a9a9 Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Tue, 5 Mar 2024 02:38:49 +0100 Subject: [PATCH 10/49] refactor: better generics with helper trait --- examples/modularize.rs | 67 ++++++++--------- src/client.rs | 166 ++++++++++------------------------------- src/lib.rs | 134 +++++++++++++++++++++++++++++++++ src/server.rs | 143 ++++++++++------------------------- tests/quinn.rs | 2 +- 5 files changed, 243 insertions(+), 269 deletions(-) diff --git a/examples/modularize.rs b/examples/modularize.rs index 13aa1ef..5fecf8e 100644 --- a/examples/modularize.rs +++ b/examples/modularize.rs @@ -81,7 +81,10 @@ mod app { calc::run_client(client.clone()).await; clock::run_client(client.clone()).await; // example for using module structs directly - let res = client.rpc_mapped(calc::AddRequest(21, 21)).await?; + let res = client + .into_service::() + .rpc(calc::AddRequest(21, 21)) + .await?; println!("rpc res: {res:?}"); Ok(()) } @@ -90,7 +93,8 @@ mod app { mod calc { use derive_more::{From, TryInto}; use quic_rpc::{ - message::RpcMsg, server::RpcChannel, RpcClient, ServiceConnection, ServiceEndpoint, + message::RpcMsg, server::RpcChannel, IntoService, RpcClient, ServiceConnection, + ServiceEndpoint, }; use serde::{Deserialize, Serialize}; use std::fmt::Debug; @@ -126,16 +130,15 @@ mod calc { pub struct Handler; impl Handler { - pub async fn handle(&self, req: Request, chan: RpcChannel) + pub async fn handle(&self, req: Request, chan: RpcChannel) where - S0: quic_rpc::Service, - S0::Req: From + Send + 'static, - S0::Res: From + Send + 'static, - E: ServiceEndpoint, + S: IntoService, + E: ServiceEndpoint, { let handler = self.clone(); + let chan = chan.into_service::(); let _res = match req { - Request::Add(req) => chan.rpc_mapped(req, handler, Self::on_add).await, + Request::Add(req) => chan.rpc(req, handler, Self::on_add).await, }; } @@ -144,18 +147,16 @@ mod calc { } } - pub async fn run_client(client: RpcClient) + pub async fn run_client(client: RpcClient) where - C: ServiceConnection, - S0: quic_rpc::Service, - Request: TryFrom + Into, - Response: TryFrom + Into, + C: ServiceConnection, + S: IntoService, { for i in 0..3 { println!("a rpc call [{i}]"); - let client = client.clone(); + let client = client.clone().into_service(); tokio::task::spawn(async move { - let res = client.rpc_mapped(AddRequest(2, i)).await; + let res = client.rpc(AddRequest(2, i)).await; println!("rpc res [{i}]: {res:?}"); }); } @@ -175,7 +176,7 @@ mod clock { use quic_rpc::{ message::{Msg, ServerStreaming, ServerStreamingMsg}, server::RpcChannel, - RpcClient, ServiceConnection, ServiceEndpoint, + IntoService, RpcClient, ServiceConnection, ServiceEndpoint, }; use serde::{Deserialize, Serialize}; use std::{ @@ -247,19 +248,15 @@ mod clock { h } - pub async fn handle(&self, req: Request, chan: RpcChannel) + pub async fn handle(&self, req: Request, chan: RpcChannel) where - S0: quic_rpc::Service, - S0::Req: From + Send + 'static, - S0::Res: From + Send + 'static, - E: ServiceEndpoint, + S: IntoService, + E: ServiceEndpoint, { let handler = self.clone(); + let chan = chan.into_service::(); let _res = match req { - Request::Tick(req) => { - chan.server_streaming_mapped(req, handler, Self::on_tick) - .await - } + Request::Tick(req) => chan.server_streaming(req, handler, Self::on_tick).await, }; } @@ -295,17 +292,15 @@ mod clock { run_client(client).await } - pub async fn run_client(client: RpcClient) + pub async fn run_client(client: RpcClient) where - C: ServiceConnection, - S0: quic_rpc::Service, - Request: TryFrom + Into, - Response: TryFrom + Into, + C: ServiceConnection, + S: IntoService, { // a rpc call for i in 0..3 { println!("a rpc call [{i}]"); - let client = client.clone(); + let client = client.clone().into_service(); tokio::task::spawn(async move { if let Err(err) = run_tick(client, i).await { tracing::warn!(?err, "client: run_tick failed"); @@ -314,14 +309,12 @@ mod clock { } } - async fn run_tick(client: RpcClient, id: usize) -> anyhow::Result<()> + async fn run_tick(client: RpcClient, id: usize) -> anyhow::Result<()> where - C: ServiceConnection, - S0: quic_rpc::Service, - Request: TryFrom + Into, - Response: TryFrom + Into, + C: ServiceConnection, + S: IntoService, { - let mut res = client.server_streaming_mapped(TickRequest).await?; + let mut res = client.server_streaming(TickRequest).await?; while let Some(res) = res.next().await { println!("stream [{id}]: {res:?}"); } diff --git a/src/client.rs b/src/client.rs index 0250d5f..fe63503 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,7 +4,7 @@ use crate::{ message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, transport::ConnectionErrors, - Service, ServiceConnection, + IntoService, Service, ServiceConnection, }; use futures::{ future::BoxFuture, stream::BoxStream, FutureExt, Sink, SinkExt, Stream, StreamExt, TryFutureExt, @@ -24,16 +24,18 @@ use std::{ /// This is a wrapper around a [ServiceConnection] that serves as the entry point /// for the client DSL. `S` is the service type, `C` is the substream source. #[derive(Debug)] -pub struct RpcClient { +pub struct RpcClient { source: C, p: PhantomData, + p2: PhantomData, } -impl Clone for RpcClient { +impl Clone for RpcClient { fn clone(&self) -> Self { Self { source: self.source.clone(), p: PhantomData, + p2: PhantomData, } } } @@ -42,51 +44,19 @@ impl Clone for RpcClient { /// that support it, [crate::message::ClientStreaming] and [crate::message::BidiStreaming]. #[pin_project] #[derive(Debug)] -pub struct UpdateSink, T: Into>( - #[pin] C::SendSink, - PhantomData, -); - -impl, T: Into> Sink for UpdateSink { - type Error = C::SendError; - - fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().0.poll_ready_unpin(cx) - } - - fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { - let req: S::Req = item.into(); - self.project().0.start_send_unpin(req) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().0.poll_flush_unpin(cx) - } - - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().0.poll_close_unpin(cx) - } -} - -/// Sink that can be used to send updates to the server for the two interaction patterns -/// that support it, [crate::message::ClientStreaming] and [crate::message::BidiStreaming]. -#[pin_project] -#[derive(Debug)] -pub struct MappedUpdateSink(#[pin] C::SendSink, PhantomData, PhantomData) +pub struct UpdateSink(#[pin] C::SendSink, PhantomData, PhantomData) where + S: IntoService, S2: Service, - S: Service, C: ServiceConnection, - T: Into, - S2::Req: Into; + T: Into; -impl Sink for MappedUpdateSink +impl Sink for UpdateSink where + S: IntoService, S2: Service, - S: Service, C: ServiceConnection, T: Into, - S2::Req: Into, { type Error = C::SendError; @@ -96,7 +66,7 @@ where fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { let req: S2::Req = item.into(); - let req: S::Req = req.into(); + let req = S::outer_req_from(req); self.project().0.start_send_unpin(req) } @@ -109,7 +79,7 @@ where } } -impl> RpcClient { +impl, C: ServiceConnection, S2: Service> RpcClient { /// Create a new rpc client for a specific [Service] given a compatible /// [ServiceConnection]. /// @@ -118,6 +88,7 @@ impl> RpcClient { Self { source, p: PhantomData, + p2: PhantomData, } } @@ -126,15 +97,21 @@ impl> RpcClient { self.source } + /// Map this channel into a derivable service channel. + pub fn into_service(self) -> RpcClient + where + S: IntoService, + { + RpcClient::::new(self.source) + } + /// RPC call to the server, single request, single response - pub async fn rpc_mapped(&self, msg: M) -> result::Result> + pub async fn rpc(&self, msg: M) -> result::Result> where M: RpcMsg, - S2: Service, - S2::Req: Into + TryFrom + Send + 'static, - S2::Res: Into + TryFrom + Send + 'static, { - let msg: ::Req = msg.into(); + let msg: S2::Req = msg.into(); + let msg = S::outer_req_from(msg); let msg: ::Req = msg.into(); let (mut send, mut recv) = self.source.open_bi().await.map_err(RpcClientError::Open)?; send.send(msg).await.map_err(RpcClientError::::Send)?; @@ -145,20 +122,12 @@ impl> RpcClient { .map_err(RpcClientError::::RecvError)?; // keep send alive until we have the answer drop(send); - let res = S2::Res::try_from(res).map_err(|_| RpcClientError::DowncastError)?; + let res = S::try_inner_res_from(res).map_err(|_| RpcClientError::DowncastError)?; M::Response::try_from(res).map_err(|_| RpcClientError::DowncastError) } - /// RPC call to the server, single request, single response - pub async fn rpc(&self, msg: M) -> result::Result> - where - M: RpcMsg, - { - self.rpc_mapped::(msg).await - } - /// Bidi call to the server, request opens a stream, response is a stream - pub async fn server_streaming_mapped( + pub async fn server_streaming( &self, msg: M, ) -> result::Result< @@ -167,12 +136,9 @@ impl> RpcClient { > where M: ServerStreamingMsg, - S2: Service, - S2::Req: Into + TryFrom + Send + 'static, - S2::Res: Into + TryFrom + Send + 'static, { let msg: S2::Req = msg.into(); - let msg: S::Req = msg.into(); + let msg = S::outer_req_from(msg); let (mut send, recv) = self .source .open_bi() @@ -183,8 +149,8 @@ impl> RpcClient { .await?; let recv = recv.map(move |x| match x { Ok(x) => { - let x = - S2::Res::try_from(x).map_err(|_| StreamingResponseItemError::DowncastError)?; + let x = S::try_inner_res_from(x) + .map_err(|_| StreamingResponseItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| StreamingResponseItemError::DowncastError) } Err(e) => Err(StreamingResponseItemError::RecvError(e)), @@ -194,46 +160,29 @@ impl> RpcClient { Ok(recv) } - /// Bidi call to the server, request opens a stream, response is a stream - pub async fn server_streaming( - &self, - msg: M, - ) -> result::Result< - BoxStream<'static, result::Result>>, - StreamingResponseError, - > - where - M: ServerStreamingMsg, - { - self.server_streaming_mapped::(msg).await - } - /// Call to the server that allows the client to stream, single response - pub async fn client_streaming_mapped( + pub async fn client_streaming( &self, msg: M, ) -> result::Result< ( - MappedUpdateSink, + UpdateSink, BoxFuture<'static, result::Result>>, ), ClientStreamingError, > where M: ClientStreamingMsg, - S2: Service, - S2::Req: Into + TryFrom + Send + 'static, - S2::Res: Into + TryFrom + Send + 'static, { let msg: S2::Req = msg.into(); - let msg: S::Req = msg.into(); + let msg = S::outer_req_from(msg); let (mut send, mut recv) = self .source .open_bi() .await .map_err(ClientStreamingError::Open)?; send.send(msg).map_err(ClientStreamingError::Send).await?; - let send = MappedUpdateSink::(send, PhantomData, PhantomData); + let send = UpdateSink::(send, PhantomData, PhantomData); let recv = async move { let item = recv .next() @@ -242,7 +191,7 @@ impl> RpcClient { match item { Ok(x) => { - let x = S2::Res::try_from(x) + let x = S::try_inner_res_from(x) .map_err(|_| ClientStreamingItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| ClientStreamingItemError::DowncastError) } @@ -253,49 +202,29 @@ impl> RpcClient { Ok((send, recv)) } - /// Call to the server that allows the client to stream, single response - pub async fn client_streaming( - &self, - msg: M, - ) -> result::Result< - ( - MappedUpdateSink, - BoxFuture<'static, result::Result>>, - ), - ClientStreamingError, - > - where - M: ClientStreamingMsg, - { - self.client_streaming_mapped::(msg).await - } - /// Bidi call to the server, request opens a stream, response is a stream - pub async fn bidi_mapped( + pub async fn bidi( &self, msg: M, ) -> result::Result< ( - MappedUpdateSink, + UpdateSink, BoxStream<'static, result::Result>>, ), BidiError, > where M: BidiStreamingMsg, - S2: Service, - S2::Req: Into + TryFrom + Send + 'static, - S2::Res: Into + TryFrom + Send + 'static, { let msg: S2::Req = msg.into(); - let msg: S::Req = msg.into(); + let msg = S::outer_req_from(msg); let (mut send, recv) = self.source.open_bi().await.map_err(BidiError::Open)?; send.send(msg).await.map_err(BidiError::::Send)?; - let send = MappedUpdateSink(send, PhantomData, PhantomData); + let send = UpdateSink(send, PhantomData, PhantomData); let recv = recv .map(|x| match x { Ok(x) => { - let x = S2::Res::try_from(x).map_err(|_| BidiItemError::DowncastError)?; + let x = S::try_inner_res_from(x).map_err(|_| BidiItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError) } Err(e) => Err(BidiItemError::RecvError(e)), @@ -303,23 +232,6 @@ impl> RpcClient { .boxed(); Ok((send, recv)) } - - /// Bidi call to the server, request opens a stream, response is a stream - pub async fn bidi( - &self, - msg: M, - ) -> result::Result< - ( - MappedUpdateSink, - BoxStream<'static, result::Result>>, - ), - BidiError, - > - where - M: BidiStreamingMsg, - { - self.bidi_mapped::(msg).await - } } impl> AsRef for RpcClient { diff --git a/src/lib.rs b/src/lib.rs index 4195b3c..d11c430 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,6 +154,140 @@ pub trait Service: Send + Sync + Debug + Clone + 'static { type Res: RpcMessage; } +// pub trait MappedService2: Service { +// type Req +// +// } +/// +// pub trait ServiceWrapperFor: Service {} +// +// impl ServiceWrapperFor for S0 +// where +// S0: Service, +// S2: Service, +// S0::Req: From, +// S0::Res: From, +// { +// } +// // +// /// +// pub trait FromService: Service { +// /// +// fn req_from(req: ::Req) -> S::Req; +// /// +// fn res_from(res: ::Res) -> S::Res; +// } +// +// impl FromService for S2 +// where +// S0: Service, +// S2: Service, +// S2::Req: Into + Send + 'static, +// S2::Res: Into + Send + 'static, +// { +// fn req_from(req: ::Req) -> S0::Req { +// req.into() +// } +// fn res_from(res: ::Res) -> S0::Res { +// res.into() +// } +// } + +/// +pub trait IntoService: Service { + /// + fn outer_req_from(req: S::Req) -> Self::Req; + /// + fn outer_res_from(res: S::Res) -> Self::Res; + /// + fn try_inner_req_from(req: Self::Req) -> Result; + /// + fn try_inner_res_from(res: Self::Res) -> Result; +} + +impl IntoService for S0 +where + S0: Service, + S2: Service, + S2::Req: Into + Send + 'static, + S2::Res: Into + Send + 'static, + + S2::Req: TryFrom + Send + 'static, + S2::Res: TryFrom + Send + 'static, +{ + fn outer_req_from(req: S2::Req) -> S0::Req { + req.into() + } + fn outer_res_from(res: S2::Res) -> S0::Res { + res.into() + } + + /// + fn try_inner_req_from(req: S0::Req) -> Result { + req.try_into().map_err(|_| ()) + } + /// + fn try_inner_res_from(res: Self::Res) -> Result { + res.try_into().map_err(|_| ()) + } + + // fn try_req_into(req: S2::Req) -> ::Req { + // req.into() + // } + // fn try_res_from(res: S2::Res) -> ::Res { + // res.into() + // } +} + +// /// +// pub trait IntoService: Service { +// fn req_into(req: ::Req) -> S::Req; +// fn res_into(res: ::Res) -> S::Res; +// } +// +// impl IntoService for S0 +// where +// S0: Service, +// S2: Service, +// S2::Req: Into + Send + 'static, +// S2::Res: Into + Send + 'static, +// { +// fn req_into(req: S2::Req) -> S0::Req { +// req.into() +// } +// fn res_into(res: S2::Res) -> S0::Res { +// res.into() +// } +// } + +// impl IntoService for S0 where S0: Service, S2: FromService { +// fn req_into(req: ::Req) -> ::Req { +// >::req_from(req) +// } +// +// fn res_into(res: ::Res) -> ::Res { +// >::res_from(res) +// } +// } + +// impl MappedService for S2 +// where +// S0: Service, +// S2: Service, +// // ::Req: From, +// // ::Res: From, +// // ::Req: From, +// // ::Res: From, +// // ::Req: From, +// // ::Res: From, +// S2::Req: Into + Send + 'static, +// S2::Res: Into + Send + 'static, +// { +// } +// S0: quic_rpc::Service, +// S0::Req: From + Send + 'static, +// S0::Res: From + Send + 'static, + /// A connection to a specific service on a specific remote machine /// /// This is just a trait alias for a [Connection] with the right types. diff --git a/src/server.rs b/src/server.rs index 0405c2a..8e67354 100644 --- a/src/server.rs +++ b/src/server.rs @@ -4,7 +4,7 @@ use crate::{ message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, transport::ConnectionErrors, - Service, ServiceEndpoint, + IntoService, Service, ServiceEndpoint, }; use futures::{channel::oneshot, task, task::Poll, Future, FutureExt, SinkExt, Stream, StreamExt}; use pin_project::pin_project; @@ -46,37 +46,58 @@ impl> RpcServer { } } -/// A channel for requests and responses for a specific service. -/// -/// This just groups the sink and stream into a single type, and attaches the -/// information about the service type. +// /// A channel for requests and responses for a specific service. +// /// +// /// This just groups the sink and stream into a single type, and attaches the +// /// information about the service type. +// /// +// /// Sink and stream are independent, so you can take the channel apart and use +// /// them independently. +// #[derive(Debug)] +// pub struct RpcChannel> { +// /// Sink to send responses to the client. +// pub send: C::SendSink, +// /// Stream to receive requests from the client. +// pub recv: C::RecvStream, +// /// Phantom data to make the type parameter `S` non-instantiable. +// p: PhantomData, +// } + /// -/// Sink and stream are independent, so you can take the channel apart and use -/// them independently. #[derive(Debug)] -pub struct RpcChannel> { +pub struct RpcChannel, C: ServiceEndpoint, S2: Service = S> { /// Sink to send responses to the client. pub send: C::SendSink, /// Stream to receive requests from the client. pub recv: C::RecvStream, /// Phantom data to make the type parameter `S` non-instantiable. p: PhantomData, + p2: PhantomData, } -impl> RpcChannel { +impl, C: ServiceEndpoint, S2: Service> RpcChannel { /// Create a new channel from a sink and a stream. pub fn new(send: C::SendSink, recv: C::RecvStream) -> Self { Self { send, recv, p: PhantomData, + p2: PhantomData, } } + /// Map this channel into a derivable service channel. + pub fn into_service(self) -> RpcChannel + where + S: IntoService, + { + RpcChannel::::new(self.send, self.recv) + } + /// handle the message of type `M` using the given function on the target object /// /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn rpc_mapped( + pub async fn rpc( self, req: M, target: T, @@ -87,9 +108,6 @@ impl> RpcChannel { F: FnOnce(T, M) -> Fut, Fut: Future, T: Send + 'static, - S2: Service, - S2::Req: Into + Send + 'static, - S2::Res: Into + Send + 'static, { let Self { mut send, mut recv, .. @@ -104,35 +122,17 @@ impl> RpcChannel { let res = f(target, req).await; // turn into a S::Res so we can send it let res: S2::Res = res.into(); - let res: S::Res = res.into(); + let res = S::outer_res_from(res); // send it and return the error if any send.send(res).await.map_err(RpcServerError::SendError) }) .await } - /// handle the message of type `M` using the given function on the target object - /// - /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn rpc( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: RpcMsg, - F: FnOnce(T, M) -> Fut, - Fut: Future, - T: Send + 'static, - { - self.rpc_mapped::(req, target, f).await - } - /// handle the message M using the given function on the target object /// /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn client_streaming_mapped( + pub async fn client_streaming( self, req: M, target: T, @@ -143,9 +143,6 @@ impl> RpcChannel { F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, Fut: Future + Send + 'static, T: Send + 'static, - S2: Service, - S2::Req: Into + Send + 'static, - S2::Res: Into + Send + 'static, { let Self { mut send, recv, .. } = self; let (updates, read_error) = UpdateStream::new(recv); @@ -154,7 +151,7 @@ impl> RpcChannel { let res = f(target, req, updates).await; // turn into a S::Res so we can send it let res: S2::Res = res.into(); - let res: S::Res = res.into(); + let res = S::outer_res_from(res); // send it and return the error if any send.send(res).await.map_err(RpcServerError::SendError) }) @@ -164,26 +161,7 @@ impl> RpcChannel { /// handle the message M using the given function on the target object /// /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn client_streaming( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: ClientStreamingMsg, - F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, - Fut: Future + Send + 'static, - T: Send + 'static, - { - self.client_streaming_mapped::(req, target, f) - .await - } - - /// handle the message M using the given function on the target object - /// - /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn bidi_streaming_mapped( + pub async fn bidi_streaming( self, req: M, target: T, @@ -194,9 +172,6 @@ impl> RpcChannel { F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, Str: Stream + Send + 'static, T: Send + 'static, - S2: Service, - S2::Req: Into + Send + 'static, - S2::Res: Into + Send + 'static, { let Self { mut send, recv, .. } = self; // downcast the updates @@ -208,7 +183,7 @@ impl> RpcChannel { while let Some(res) = responses.next().await { // turn into a S::Res so we can send it let res: S2::Res = res.into(); - let res: S::Res = res.into(); + let res = S::outer_res_from(res); // send it and return the error if any send.send(res).await.map_err(RpcServerError::SendError)?; } @@ -220,25 +195,7 @@ impl> RpcChannel { /// handle the message M using the given function on the target object /// /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn bidi_streaming( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: BidiStreamingMsg, - F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, - Str: Stream + Send + 'static, - T: Send + 'static, - { - self.bidi_streaming_mapped::(req, target, f).await - } - - /// handle the message M using the given function on the target object - /// - /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn server_streaming_mapped( + pub async fn server_streaming( self, req: M, target: T, @@ -249,9 +206,6 @@ impl> RpcChannel { F: FnOnce(T, M) -> Str + Send + 'static, Str: Stream + Send + 'static, T: Send + 'static, - S2: Service, - S2::Req: Into + Send + 'static, - S2::Res: Into + Send + 'static, { let Self { mut send, mut recv, .. @@ -268,7 +222,7 @@ impl> RpcChannel { while let Some(response) = responses.next().await { // turn into a S::Res so we can send it let response: S2::Res = response.into(); - let response: S::Res = response.into(); + let response = S::outer_res_from(response); // send it and return the error if any send.send(response) .await @@ -279,25 +233,6 @@ impl> RpcChannel { .await } - /// handle the message M using the given function on the target object - /// - /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn server_streaming( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: ServerStreamingMsg, - F: FnOnce(T, M) -> Str + Send + 'static, - Str: Stream + Send + 'static, - T: Send + 'static, - { - self.server_streaming_mapped::(req, target, f) - .await - } - /// A rpc call that also maps the error from the user type to the wire type /// /// This is useful if you want to write your function with a convenient error type like anyhow::Error, @@ -309,7 +244,7 @@ impl> RpcChannel { f: F, ) -> result::Result<(), RpcServerError> where - M: RpcMsg>, + M: RpcMsg>, F: FnOnce(T, M) -> Fut, Fut: Future>, E2: From, diff --git a/tests/quinn.rs b/tests/quinn.rs index de5fc1f..67b0f72 100644 --- a/tests/quinn.rs +++ b/tests/quinn.rs @@ -150,7 +150,7 @@ async fn server_away_and_back() -> anyhow::Result<()> { let client = make_client_endpoint("0.0.0.0:0".parse()?, &[&server_cert])?; let client_connection = transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); - let client = RpcClient::new(client_connection); + let client = RpcClient::::new(client_connection); // send a request. No server available so it should fail client.rpc(Sqr(4)).await.unwrap_err(); From dd7810cbefcfcd80d0269dded305c26f5c7656b0 Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Tue, 5 Mar 2024 03:19:34 +0100 Subject: [PATCH 11/49] cleanup --- Cargo.toml | 2 +- examples/modularize.rs | 190 +++++++++++++++++++++-------------------- src/client.rs | 10 +-- src/lib.rs | 125 ++++----------------------- src/server.rs | 28 ++---- 5 files changed, 123 insertions(+), 232 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0af378c..93fe349 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ quinn-transport = ["flume", "quinn", "bincode", "tokio-serde", "tokio-util"] flume-transport = ["flume"] combined-transport = [] macros = [] -default = ["flume-transport"] +default = [] [[example]] name = "errors" diff --git a/examples/modularize.rs b/examples/modularize.rs index 5fecf8e..09fef1c 100644 --- a/examples/modularize.rs +++ b/examples/modularize.rs @@ -1,32 +1,26 @@ -use quic_rpc::{transport::flume, RpcServer, ServiceEndpoint}; +use quic_rpc::{transport::flume, RpcServer}; use tracing::warn; -async fn run_server( - server_ep: impl ServiceEndpoint, - handler: app::Handler, -) -> anyhow::Result<()> { - let server = RpcServer::new(server_ep); - loop { - let (req, chan) = server.accept().await?; - let handler = handler.clone(); - tokio::task::spawn(async move { - handler.handle(req, chan).await; - }); - } -} - #[tokio::main] async fn main() -> anyhow::Result<()> { let (server_conn, client_conn) = flume::connection::(1); - let handler = app::Handler::default(); - let server_handle = tokio::task::spawn(async move { - if let Err(err) = run_server(server_conn, handler).await { - warn!(?err, "RPC server failed"); - }; + + tokio::task::spawn(async move { + let server = RpcServer::new(server_conn); + let handler = app::Handler::default(); + loop { + match server.accept().await { + Err(err) => warn!(?err, "server accept failed"), + Ok((req, chan)) => { + let handler = handler.clone(); + tokio::task::spawn(handler.handle_rpc_request(req, chan)); + } + } + } }); app::client_demo(client_conn).await?; - server_handle.await?; + Ok(()) } @@ -34,7 +28,8 @@ mod app { use super::{calc, clock}; use anyhow::Result; use derive_more::{From, TryInto}; - use quic_rpc::{server::RpcChannel, ServiceConnection, ServiceEndpoint}; + use futures::StreamExt; + use quic_rpc::{server::RpcChannel, RpcClient, ServiceConnection, ServiceEndpoint}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, From, TryInto)] @@ -50,8 +45,8 @@ mod app { } #[derive(Copy, Clone, Debug)] - pub struct Service; - impl quic_rpc::Service for Service { + pub struct AppService; + impl quic_rpc::Service for AppService { type Req = Request; type Res = Response; } @@ -63,29 +58,45 @@ mod app { } impl Handler { - pub async fn handle>( - &self, + pub async fn handle_rpc_request>( + self, req: Request, - chan: RpcChannel, + chan: RpcChannel, ) { let _res = match req { - Request::Calc(req) => self.calc.handle(req, chan).await, - Request::Clock(req) => self.clock.handle(req, chan).await, + Request::Calc(req) => self.calc.handle_rpc_request(req, chan).await, + Request::Clock(req) => self.clock.handle_rpc_request(req, chan).await, }; } } - pub async fn client_demo>(conn: C) -> Result<()> { - let client = quic_rpc::RpcClient::::new(conn); - // examples for generic handlers - calc::run_client(client.clone()).await; - clock::run_client(client.clone()).await; - // example for using module structs directly - let res = client - .into_service::() - .rpc(calc::AddRequest(21, 21)) - .await?; - println!("rpc res: {res:?}"); + #[derive(Debug, Clone)] + pub struct Client> { + pub calc: calc::Client, + pub clock: clock::Client, + } + + impl> Client { + pub fn new(conn: C) -> Self { + let client = RpcClient::::new(conn); + Self { + calc: calc::Client::new(client.clone()), + clock: clock::Client::new(client.clone()), + } + } + } + + pub async fn client_demo>(conn: C) -> Result<()> { + let client = Client::new(conn); + println!("calc service: add"); + let res = client.calc.add(40, 2).await?; + println!("calc service: res {res:?}"); + println!("clock service: start tick"); + let mut stream = client.clock.tick().await?; + while let Some(tick) = stream.next().await { + let tick = tick?; + println!("clock service: tick {tick}"); + } Ok(()) } } @@ -102,7 +113,7 @@ mod calc { #[derive(Debug, Serialize, Deserialize)] pub struct AddRequest(pub i64, pub i64); - impl RpcMsg for AddRequest { + impl RpcMsg for AddRequest { type Response = AddResponse; } @@ -120,8 +131,8 @@ mod calc { } #[derive(Copy, Clone, Debug)] - pub struct Service; - impl quic_rpc::Service for Service { + pub struct CalcService; + impl quic_rpc::Service for CalcService { type Req = Request; type Res = Response; } @@ -130,13 +141,13 @@ mod calc { pub struct Handler; impl Handler { - pub async fn handle(&self, req: Request, chan: RpcChannel) + pub async fn handle_rpc_request(&self, req: Request, chan: RpcChannel) where - S: IntoService, + S: IntoService, E: ServiceEndpoint, { let handler = self.clone(); - let chan = chan.into_service::(); + let chan = chan.service::(); let _res = match req { Request::Add(req) => chan.rpc(req, handler, Self::on_add).await, }; @@ -147,32 +158,36 @@ mod calc { } } - pub async fn run_client(client: RpcClient) + #[derive(Debug, Clone)] + pub struct Client where C: ServiceConnection, - S: IntoService, + S: IntoService, { - for i in 0..3 { - println!("a rpc call [{i}]"); - let client = client.clone().into_service(); - tokio::task::spawn(async move { - let res = client.rpc(AddRequest(2, i)).await; - println!("rpc res [{i}]: {res:?}"); - }); - } + client: RpcClient, } - #[allow(unused)] - pub async fn client_demo>(conn: C) { - let client = quic_rpc::RpcClient::::new(conn); - run_client(client).await; + impl Client + where + C: ServiceConnection, + S: IntoService, + { + pub fn new(client: RpcClient) -> Self { + Self { + client: client.service(), + } + } + pub async fn add(&self, a: i64, b: i64) -> anyhow::Result { + let res = self.client.rpc(AddRequest(a, b)).await?; + Ok(res.0) + } } } mod clock { use anyhow::Result; use derive_more::{From, TryInto}; - use futures::{Stream, StreamExt}; + use futures::{stream::BoxStream, Stream, StreamExt, TryStreamExt}; use quic_rpc::{ message::{Msg, ServerStreaming, ServerStreamingMsg}, server::RpcChannel, @@ -189,11 +204,11 @@ mod clock { #[derive(Debug, Serialize, Deserialize)] pub struct TickRequest; - impl Msg for TickRequest { + impl Msg for TickRequest { type Pattern = ServerStreaming; } - impl ServerStreamingMsg for TickRequest { + impl ServerStreamingMsg for TickRequest { type Response = TickResponse; } @@ -213,8 +228,8 @@ mod clock { } #[derive(Copy, Clone, Debug)] - pub struct Service; - impl quic_rpc::Service for Service { + pub struct ClockService; + impl quic_rpc::Service for ClockService { type Req = Request; type Res = Response; } @@ -248,13 +263,13 @@ mod clock { h } - pub async fn handle(&self, req: Request, chan: RpcChannel) + pub async fn handle_rpc_request(&self, req: Request, chan: RpcChannel) where - S: IntoService, + S: IntoService, E: ServiceEndpoint, { let handler = self.clone(); - let chan = chan.into_service::(); + let chan = chan.service::(); let _res = match req { Request::Tick(req) => chan.server_streaming(req, handler, Self::on_tick).await, }; @@ -286,39 +301,28 @@ mod clock { } } - #[allow(unused)] - pub async fn client_demo>(conn: C) { - let client = RpcClient::::new(conn); - run_client(client).await - } - - pub async fn run_client(client: RpcClient) + #[derive(Debug, Clone)] + pub struct Client where C: ServiceConnection, - S: IntoService, + S: IntoService, { - // a rpc call - for i in 0..3 { - println!("a rpc call [{i}]"); - let client = client.clone().into_service(); - tokio::task::spawn(async move { - if let Err(err) = run_tick(client, i).await { - tracing::warn!(?err, "client: run_tick failed"); - } - }); - } + client: RpcClient, } - async fn run_tick(client: RpcClient, id: usize) -> anyhow::Result<()> + impl Client where C: ServiceConnection, - S: IntoService, + S: IntoService, { - let mut res = client.server_streaming(TickRequest).await?; - while let Some(res) = res.next().await { - println!("stream [{id}]: {res:?}"); + pub fn new(client: RpcClient) -> Self { + Self { + client: client.service(), + } + } + pub async fn tick(&self) -> Result>> { + let res = self.client.server_streaming(TickRequest).await?; + Ok(res.map_ok(|r| r.tick).map_err(anyhow::Error::from).boxed()) } - println!("stream [{id}]: done"); - Ok(()) } } diff --git a/src/client.rs b/src/client.rs index fe63503..bb74420 100644 --- a/src/client.rs +++ b/src/client.rs @@ -65,8 +65,7 @@ where } fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { - let req: S2::Req = item.into(); - let req = S::outer_req_from(req); + let req = S::outer_req_from(item); self.project().0.start_send_unpin(req) } @@ -98,7 +97,7 @@ impl, C: ServiceConnection, S2: Service> RpcClie } /// Map this channel into a derivable service channel. - pub fn into_service(self) -> RpcClient + pub fn service(self) -> RpcClient where S: IntoService, { @@ -110,9 +109,7 @@ impl, C: ServiceConnection, S2: Service> RpcClie where M: RpcMsg, { - let msg: S2::Req = msg.into(); let msg = S::outer_req_from(msg); - let msg: ::Req = msg.into(); let (mut send, mut recv) = self.source.open_bi().await.map_err(RpcClientError::Open)?; send.send(msg).await.map_err(RpcClientError::::Send)?; let res = recv @@ -137,7 +134,6 @@ impl, C: ServiceConnection, S2: Service> RpcClie where M: ServerStreamingMsg, { - let msg: S2::Req = msg.into(); let msg = S::outer_req_from(msg); let (mut send, recv) = self .source @@ -174,7 +170,6 @@ impl, C: ServiceConnection, S2: Service> RpcClie where M: ClientStreamingMsg, { - let msg: S2::Req = msg.into(); let msg = S::outer_req_from(msg); let (mut send, mut recv) = self .source @@ -216,7 +211,6 @@ impl, C: ServiceConnection, S2: Service> RpcClie where M: BidiStreamingMsg, { - let msg: S2::Req = msg.into(); let msg = S::outer_req_from(msg); let (mut send, recv) = self.source.open_bi().await.map_err(BidiError::Open)?; send.send(msg).await.map_err(BidiError::::Send)?; diff --git a/src/lib.rs b/src/lib.rs index d11c430..4849684 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,54 +154,18 @@ pub trait Service: Send + Sync + Debug + Clone + 'static { type Res: RpcMessage; } -// pub trait MappedService2: Service { -// type Req -// -// } -/// -// pub trait ServiceWrapperFor: Service {} -// -// impl ServiceWrapperFor for S0 -// where -// S0: Service, -// S2: Service, -// S0::Req: From, -// S0::Res: From, -// { -// } -// // -// /// -// pub trait FromService: Service { -// /// -// fn req_from(req: ::Req) -> S::Req; -// /// -// fn res_from(res: ::Res) -> S::Res; -// } -// -// impl FromService for S2 -// where -// S0: Service, -// S2: Service, -// S2::Req: Into + Send + 'static, -// S2::Res: Into + Send + 'static, -// { -// fn req_from(req: ::Req) -> S0::Req { -// req.into() -// } -// fn res_from(res: ::Res) -> S0::Res { -// res.into() -// } -// } - +/// Marker trait for services that can be mapped into another service. /// +/// There is usually no need to impl this trait manually, because it is auto-implemented as long as +/// the inner service's Req and Res types implement Into and TryFrom to the outer service's types. pub trait IntoService: Service { - /// - fn outer_req_from(req: S::Req) -> Self::Req; - /// - fn outer_res_from(res: S::Res) -> Self::Res; - /// + /// Convert the inner request into the outer request. + fn outer_req_from(req: impl Into) -> Self::Req; + /// Convert the inner response into the outer response. + fn outer_res_from(res: impl Into) -> Self::Res; + /// Try to convert the outer request into the inner request. fn try_inner_req_from(req: Self::Req) -> Result; - /// + /// Try to convert the outer response into the inner response. fn try_inner_res_from(res: Self::Res) -> Result; } @@ -211,83 +175,26 @@ where S2: Service, S2::Req: Into + Send + 'static, S2::Res: Into + Send + 'static, - S2::Req: TryFrom + Send + 'static, S2::Res: TryFrom + Send + 'static, { - fn outer_req_from(req: S2::Req) -> S0::Req { - req.into() + fn outer_req_from(req: impl Into) -> S0::Req { + (req.into()).into() } - fn outer_res_from(res: S2::Res) -> S0::Res { - res.into() + + fn outer_res_from(res: impl Into) -> S0::Res { + (res.into()).into() } - /// - fn try_inner_req_from(req: S0::Req) -> Result { + fn try_inner_req_from(req: Self::Req) -> Result { req.try_into().map_err(|_| ()) } - /// + fn try_inner_res_from(res: Self::Res) -> Result { res.try_into().map_err(|_| ()) } - - // fn try_req_into(req: S2::Req) -> ::Req { - // req.into() - // } - // fn try_res_from(res: S2::Res) -> ::Res { - // res.into() - // } } -// /// -// pub trait IntoService: Service { -// fn req_into(req: ::Req) -> S::Req; -// fn res_into(res: ::Res) -> S::Res; -// } -// -// impl IntoService for S0 -// where -// S0: Service, -// S2: Service, -// S2::Req: Into + Send + 'static, -// S2::Res: Into + Send + 'static, -// { -// fn req_into(req: S2::Req) -> S0::Req { -// req.into() -// } -// fn res_into(res: S2::Res) -> S0::Res { -// res.into() -// } -// } - -// impl IntoService for S0 where S0: Service, S2: FromService { -// fn req_into(req: ::Req) -> ::Req { -// >::req_from(req) -// } -// -// fn res_into(res: ::Res) -> ::Res { -// >::res_from(res) -// } -// } - -// impl MappedService for S2 -// where -// S0: Service, -// S2: Service, -// // ::Req: From, -// // ::Res: From, -// // ::Req: From, -// // ::Res: From, -// // ::Req: From, -// // ::Res: From, -// S2::Req: Into + Send + 'static, -// S2::Res: Into + Send + 'static, -// { -// } -// S0: quic_rpc::Service, -// S0::Req: From + Send + 'static, -// S0::Res: From + Send + 'static, - /// A connection to a specific service on a specific remote machine /// /// This is just a trait alias for a [Connection] with the right types. diff --git a/src/server.rs b/src/server.rs index 8e67354..86fc953 100644 --- a/src/server.rs +++ b/src/server.rs @@ -46,24 +46,13 @@ impl> RpcServer { } } -// /// A channel for requests and responses for a specific service. -// /// -// /// This just groups the sink and stream into a single type, and attaches the -// /// information about the service type. -// /// -// /// Sink and stream are independent, so you can take the channel apart and use -// /// them independently. -// #[derive(Debug)] -// pub struct RpcChannel> { -// /// Sink to send responses to the client. -// pub send: C::SendSink, -// /// Stream to receive requests from the client. -// pub recv: C::RecvStream, -// /// Phantom data to make the type parameter `S` non-instantiable. -// p: PhantomData, -// } - +/// A channel for requests and responses for a specific service. +/// +/// This just groups the sink and stream into a single type, and attaches the +/// information about the service type. /// +/// Sink and stream are independent, so you can take the channel apart and use +/// them independently. #[derive(Debug)] pub struct RpcChannel, C: ServiceEndpoint, S2: Service = S> { /// Sink to send responses to the client. @@ -87,7 +76,7 @@ impl, C: ServiceEndpoint, S2: Service> RpcChannel(self) -> RpcChannel + pub fn service(self) -> RpcChannel where S: IntoService, { @@ -150,7 +139,6 @@ impl, C: ServiceEndpoint, S2: Service> RpcChannel, C: ServiceEndpoint, S2: Service> RpcChannel, C: ServiceEndpoint, S2: Service> RpcChannel Date: Tue, 5 Mar 2024 03:49:02 +0100 Subject: [PATCH 12/49] refactor: naming --- examples/modularize.rs | 55 +++++++++++++++++++----------------------- src/client.rs | 2 +- src/server.rs | 9 +++---- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/examples/modularize.rs b/examples/modularize.rs index 09fef1c..429002d 100644 --- a/examples/modularize.rs +++ b/examples/modularize.rs @@ -25,13 +25,14 @@ async fn main() -> anyhow::Result<()> { } mod app { - use super::{calc, clock}; use anyhow::Result; use derive_more::{From, TryInto}; use futures::StreamExt; use quic_rpc::{server::RpcChannel, RpcClient, ServiceConnection, ServiceEndpoint}; use serde::{Deserialize, Serialize}; + use super::{calc, clock}; + #[derive(Debug, Serialize, Deserialize, From, TryInto)] pub enum Request { Calc(calc::Request), @@ -64,21 +65,21 @@ mod app { chan: RpcChannel, ) { let _res = match req { - Request::Calc(req) => self.calc.handle_rpc_request(req, chan).await, - Request::Clock(req) => self.clock.handle_rpc_request(req, chan).await, + Request::Calc(req) => self.calc.handle_rpc_request(req, chan.map()).await, + Request::Clock(req) => self.clock.handle_rpc_request(req, chan.map()).await, }; } } #[derive(Debug, Clone)] pub struct Client> { - pub calc: calc::Client, - pub clock: clock::Client, + pub calc: calc::Client, + pub clock: clock::Client, } impl> Client { pub fn new(conn: C) -> Self { - let client = RpcClient::::new(conn); + let client = RpcClient::new(conn); Self { calc: calc::Client::new(client.clone()), clock: clock::Client::new(client.clone()), @@ -141,15 +142,16 @@ mod calc { pub struct Handler; impl Handler { - pub async fn handle_rpc_request(&self, req: Request, chan: RpcChannel) - where + pub async fn handle_rpc_request( + self, + req: Request, + chan: RpcChannel, + ) where S: IntoService, E: ServiceEndpoint, { - let handler = self.clone(); - let chan = chan.service::(); let _res = match req { - Request::Add(req) => chan.rpc(req, handler, Self::on_add).await, + Request::Add(req) => chan.rpc(req, self, Self::on_add).await, }; } @@ -159,22 +161,18 @@ mod calc { } #[derive(Debug, Clone)] - pub struct Client - where - C: ServiceConnection, - S: IntoService, - { + pub struct Client { client: RpcClient, } - impl Client + impl Client where C: ServiceConnection, S: IntoService, { pub fn new(client: RpcClient) -> Self { Self { - client: client.service(), + client: client.map(), } } pub async fn add(&self, a: i64, b: i64) -> anyhow::Result { @@ -263,15 +261,16 @@ mod clock { h } - pub async fn handle_rpc_request(&self, req: Request, chan: RpcChannel) - where + pub async fn handle_rpc_request( + self, + req: Request, + chan: RpcChannel, + ) where S: IntoService, E: ServiceEndpoint, { - let handler = self.clone(); - let chan = chan.service::(); let _res = match req { - Request::Tick(req) => chan.server_streaming(req, handler, Self::on_tick).await, + Request::Tick(req) => chan.server_streaming(req, self, Self::on_tick).await, }; } @@ -302,22 +301,18 @@ mod clock { } #[derive(Debug, Clone)] - pub struct Client - where - C: ServiceConnection, - S: IntoService, - { + pub struct Client { client: RpcClient, } - impl Client + impl Client where C: ServiceConnection, S: IntoService, { pub fn new(client: RpcClient) -> Self { Self { - client: client.service(), + client: client.map(), } } pub async fn tick(&self) -> Result>> { diff --git a/src/client.rs b/src/client.rs index bb74420..3720c70 100644 --- a/src/client.rs +++ b/src/client.rs @@ -97,7 +97,7 @@ impl, C: ServiceConnection, S2: Service> RpcClie } /// Map this channel into a derivable service channel. - pub fn service(self) -> RpcClient + pub fn map(self) -> RpcClient where S: IntoService, { diff --git a/src/server.rs b/src/server.rs index 86fc953..c8e7139 100644 --- a/src/server.rs +++ b/src/server.rs @@ -76,7 +76,7 @@ impl, C: ServiceEndpoint, S2: Service> RpcChannel(self) -> RpcChannel + pub fn map(self) -> RpcChannel where S: IntoService, { @@ -110,7 +110,6 @@ impl, C: ServiceEndpoint, S2: Service> RpcChannel, C: ServiceEndpoint, S2: Service> RpcChannel Date: Tue, 5 Mar 2024 04:01:34 +0100 Subject: [PATCH 13/49] cleanup --- src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4849684..d4456c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,10 +173,8 @@ impl IntoService for S0 where S0: Service, S2: Service, - S2::Req: Into + Send + 'static, - S2::Res: Into + Send + 'static, - S2::Req: TryFrom + Send + 'static, - S2::Res: TryFrom + Send + 'static, + S2::Req: Into + TryFrom + Send + 'static, + S2::Res: Into + TryFrom + Send + 'static, { fn outer_req_from(req: impl Into) -> S0::Req { (req.into()).into() From 3b1a783f58580e99a6e88887ee0aa0e4a8692177 Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Tue, 5 Mar 2024 11:40:48 +0100 Subject: [PATCH 14/49] better docs --- examples/modularize.rs | 11 +++++++++++ src/client.rs | 5 ++++- src/lib.rs | 10 +++++++++- src/server.rs | 5 ++++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/examples/modularize.rs b/examples/modularize.rs index 429002d..4cc3e25 100644 --- a/examples/modularize.rs +++ b/examples/modularize.rs @@ -1,3 +1,14 @@ +//! This example shows how an RPC service can be modularized, even between different crates. +//! +//! The [`calc`] and [`clock`] modules both expose a [`quic_rpc::Service`] in a regular fashion. +//! They do not `use` anything from `super` or `app` so they could live in their own crates +//! unchanged. The only difference to other examples is that their handlers take a generic +//! `S: IntoService`, which allows to pass in any service that can be mapped to +//! the module's service. +//! +//! The [`app`] module depends on both `calc` and `clock` and composes both their servers and +//! clients into a single app handler / client. + use quic_rpc::{transport::flume, RpcServer}; use tracing::warn; diff --git a/src/client.rs b/src/client.rs index 3720c70..0c21d15 100644 --- a/src/client.rs +++ b/src/client.rs @@ -96,7 +96,10 @@ impl, C: ServiceConnection, S2: Service> RpcClie self.source } - /// Map this channel into a derivable service channel. + /// Map this channel's service into an inner service. + /// + /// This method is available as long as the outer service implements [`IntoService`] for the + /// inner service. pub fn map(self) -> RpcClient where S: IntoService, diff --git a/src/lib.rs b/src/lib.rs index d4456c3..11b9b12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,8 +156,16 @@ pub trait Service: Send + Sync + Debug + Clone + 'static { /// Marker trait for services that can be mapped into another service. /// +/// This can be used in place of the usual `S: Service` generic to accept any service that can be +/// mapped to the service in question. +/// /// There is usually no need to impl this trait manually, because it is auto-implemented as long as -/// the inner service's Req and Res types implement Into and TryFrom to the outer service's types. +/// the inner service's Req and Res types implement [`Into`] and [`TryFrom`] to the outer service's types. +/// +/// See `examples/modularize.rs` for an example of how to use this trait to decouple RPC services +/// between different, independent modules or crates. +/// +/// If an outer service impls [`IntoService`] for an inner service, the [`crate::RpcChannel`] and [`RpcClient`] pub trait IntoService: Service { /// Convert the inner request into the outer request. fn outer_req_from(req: impl Into) -> Self::Req; diff --git a/src/server.rs b/src/server.rs index c8e7139..ac496e2 100644 --- a/src/server.rs +++ b/src/server.rs @@ -75,7 +75,10 @@ impl, C: ServiceEndpoint, S2: Service> RpcChannel(self) -> RpcChannel where S: IntoService, From 54c4adeef2c851cbe2e6ac542d221b6f020a430c Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Thu, 7 Mar 2024 21:37:25 +0100 Subject: [PATCH 15/49] feat: add additional `Sync` bounds to allow for better reuse of streams --- src/client.rs | 16 +++++++++------- src/transport/misc/mod.rs | 2 +- src/transport/mod.rs | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/client.rs b/src/client.rs index a5fb86f..e5d04d7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -7,7 +7,7 @@ use crate::{ Service, ServiceConnection, }; use futures::{ - future::BoxFuture, stream::BoxStream, FutureExt, Sink, SinkExt, Stream, StreamExt, TryFutureExt, + future::BoxFuture, FutureExt, Sink, SinkExt, Stream, StreamExt, TryFutureExt, }; use pin_project::pin_project; use std::{ @@ -19,6 +19,9 @@ use std::{ task::{Context, Poll}, }; +/// Sync version of `future::stream::BoxStream`. +pub type BoxStreamSync<'a, T> = Pin + Send + Sync + 'a>>; + /// A client for a specific service /// /// This is a wrapper around a [ServiceConnection] that serves as the entry point @@ -108,7 +111,7 @@ impl> RpcClient { &self, msg: M, ) -> result::Result< - BoxStream<'static, result::Result>>, + BoxStreamSync<'static, result::Result>>, StreamingResponseError, > where @@ -130,7 +133,7 @@ impl> RpcClient { Err(e) => Err(StreamingResponseItemError::RecvError(e)), }); // keep send alive so the request on the server side does not get cancelled - let recv = DeferDrop(recv, send).boxed(); + let recv = Box::pin(DeferDrop(recv, send)); Ok(recv) } @@ -180,7 +183,7 @@ impl> RpcClient { ) -> result::Result< ( UpdateSink, - BoxStream<'static, result::Result>>, + BoxStreamSync<'static, result::Result>>, ), BidiError, > @@ -191,12 +194,11 @@ impl> RpcClient { let (mut send, recv) = self.source.open_bi().await.map_err(BidiError::Open)?; send.send(msg).await.map_err(BidiError::::Send)?; let send = UpdateSink(send, PhantomData); - let recv = recv + let recv = Box::pin(recv .map(|x| match x { Ok(x) => M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError), Err(e) => Err(BidiItemError::RecvError(e)), - }) - .boxed(); + })); Ok((send, recv)) } } diff --git a/src/transport/misc/mod.rs b/src/transport/misc/mod.rs index f48e531..598b68c 100644 --- a/src/transport/misc/mod.rs +++ b/src/transport/misc/mod.rs @@ -24,7 +24,7 @@ impl ConnectionErrors for DummyServerEndpoint { impl ConnectionCommon for DummyServerEndpoint { type RecvStream = stream::Pending>; - type SendSink = Box + Unpin + Send>; + type SendSink = Box + Unpin + Send + Sync>; } impl ServerEndpoint for DummyServerEndpoint { diff --git a/src/transport/mod.rs b/src/transport/mod.rs index d8e0f03..9d03b3e 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -34,9 +34,9 @@ pub trait ConnectionErrors: Debug + Clone + Send + Sync + 'static { /// Having this as a separate trait is useful when writing generic code that works with both. pub trait ConnectionCommon: ConnectionErrors { /// Receive side of a bidirectional typed channel - type RecvStream: Stream> + Send + Unpin + 'static; + type RecvStream: Stream> + Send + Sync + Unpin + 'static; /// Send side of a bidirectional typed channel - type SendSink: Sink + Send + Unpin + 'static; + type SendSink: Sink + Send + Sync + Unpin + 'static; } /// A connection to a specific remote machine From 605a817b2a529fa22a2628ccd8d9aa8812e77e26 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Thu, 7 Mar 2024 21:48:13 +0100 Subject: [PATCH 16/49] fmt --- src/client.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/client.rs b/src/client.rs index e5d04d7..03863b7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -6,9 +6,7 @@ use crate::{ transport::ConnectionErrors, Service, ServiceConnection, }; -use futures::{ - future::BoxFuture, FutureExt, Sink, SinkExt, Stream, StreamExt, TryFutureExt, -}; +use futures::{future::BoxFuture, FutureExt, Sink, SinkExt, Stream, StreamExt, TryFutureExt}; use pin_project::pin_project; use std::{ error, @@ -194,11 +192,10 @@ impl> RpcClient { let (mut send, recv) = self.source.open_bi().await.map_err(BidiError::Open)?; send.send(msg).await.map_err(BidiError::::Send)?; let send = UpdateSink(send, PhantomData); - let recv = Box::pin(recv - .map(|x| match x { - Ok(x) => M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError), - Err(e) => Err(BidiItemError::RecvError(e)), - })); + let recv = Box::pin(recv.map(|x| match x { + Ok(x) => M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError), + Err(e) => Err(BidiItemError::RecvError(e)), + })); Ok((send, recv)) } } From 92b9b60beeb933bc85a8f977242336a504b735f6 Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Fri, 8 Mar 2024 12:51:58 +0100 Subject: [PATCH 17/49] wip: better approach - setup --- examples/modularize.rs | 170 ++++++++++++++++++++++++++++++++++------- src/client.rs | 18 ++--- src/lib.rs | 16 ++-- src/server.rs | 8 +- 4 files changed, 163 insertions(+), 49 deletions(-) diff --git a/examples/modularize.rs b/examples/modularize.rs index 4cc3e25..bb73516 100644 --- a/examples/modularize.rs +++ b/examples/modularize.rs @@ -2,7 +2,7 @@ //! //! The [`calc`] and [`clock`] modules both expose a [`quic_rpc::Service`] in a regular fashion. //! They do not `use` anything from `super` or `app` so they could live in their own crates -//! unchanged. The only difference to other examples is that their handlers take a generic +//! unchanged. The only difference to other examples is that their handlers take a generic //! `S: IntoService`, which allows to pass in any service that can be mapped to //! the module's service. //! @@ -24,7 +24,11 @@ async fn main() -> anyhow::Result<()> { Err(err) => warn!(?err, "server accept failed"), Ok((req, chan)) => { let handler = handler.clone(); - tokio::task::spawn(handler.handle_rpc_request(req, chan)); + tokio::task::spawn(async move { + if let Err(err) = handler.handle_rpc_request(req, chan) { + warn!(?err, "internal rpc error"); + } + }); } } } @@ -36,26 +40,41 @@ async fn main() -> anyhow::Result<()> { } mod app { + //! This is the app-specific code. + //! + //! It uses all of iroh (calc + clock) plus an app-specific endpoint use anyhow::Result; use derive_more::{From, TryInto}; use futures::StreamExt; - use quic_rpc::{server::RpcChannel, RpcClient, ServiceConnection, ServiceEndpoint}; + use quic_rpc::{ + message::RpcMsg, server::RpcChannel, RpcClient, ServiceConnection, ServiceEndpoint, + }; use serde::{Deserialize, Serialize}; - use super::{calc, clock}; + use super::iroh; #[derive(Debug, Serialize, Deserialize, From, TryInto)] pub enum Request { - Calc(calc::Request), - Clock(clock::Request), + Iroh(iroh::Request), + AppVersion(AppVersionRequest), } #[derive(Debug, Serialize, Deserialize, From, TryInto)] pub enum Response { - Calc(calc::Response), - Clock(clock::Response), + Iroh(iroh::Response), + AppVersion(AppVersionResponse), + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct AppVersionRequest; + + impl RpcMsg for AppVersionRequest { + type Response = AppVersionResponse; } + #[derive(Debug, Serialize, Deserialize)] + pub struct AppVersionResponse(pub String); + #[derive(Copy, Clone, Debug)] pub struct AppService; impl quic_rpc::Service for AppService { @@ -65,8 +84,8 @@ mod app { #[derive(Clone, Default)] pub struct Handler { - calc: calc::Handler, - clock: clock::Handler, + iroh: iroh::Handler, + app_version: String, } impl Handler { @@ -74,37 +93,50 @@ mod app { self, req: Request, chan: RpcChannel, - ) { - let _res = match req { - Request::Calc(req) => self.calc.handle_rpc_request(req, chan.map()).await, - Request::Clock(req) => self.clock.handle_rpc_request(req, chan.map()).await, + ) -> Result<()> { + match req { + Request::Iroh(req) => self.iroh.handle_rpc_request(req, chan.map()).await?, + Request::AppVersion(req) => chan.rpc(req, self, Self::on_version).await?, }; + Ok(()) + } + + pub async fn on_version(self, req: AppVersionRequest) -> AppVersionResponse { + AppVersionResponse(self.version.clone()) } } #[derive(Debug, Clone)] pub struct Client> { - pub calc: calc::Client, - pub clock: clock::Client, + pub iroh: iroh::Client, + client: RpcClient, } impl> Client { pub fn new(conn: C) -> Self { let client = RpcClient::new(conn); Self { - calc: calc::Client::new(client.clone()), - clock: clock::Client::new(client.clone()), + iroh: iroh::Client::new(client.clone()), + client, } } + + pub async fn app_version(&self) -> Result { + let res = self.client.rpc(AppVersionRequest).await?; + Ok(res.0) + } } pub async fn client_demo>(conn: C) -> Result<()> { let client = Client::new(conn); + println!("app service: version"); + let res = client.app_version().await?; + println!("app service: version res {res:?}"); println!("calc service: add"); - let res = client.calc.add(40, 2).await?; + let res = client.iroh.calc.add(40, 2).await?; println!("calc service: res {res:?}"); println!("clock service: start tick"); - let mut stream = client.clock.tick().await?; + let mut stream = client.iroh.clock.tick().await?; while let Some(tick) = stream.next().await { let tick = tick?; println!("clock service: tick {tick}"); @@ -113,7 +145,85 @@ mod app { } } +mod iroh { + //! This module composes two sub-services + + use anyhow::Result; + use derive_more::{From, TryInto}; + use futures::StreamExt; + use quic_rpc::{ + server::RpcChannel, IntoService, RpcClient, ServiceConnection, ServiceEndpoint, + }; + use serde::{Deserialize, Serialize}; + + use super::{calc, clock}; + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Request { + Calc(calc::Request), + Clock(clock::Request), + } + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + pub enum Response { + Calc(calc::Response), + Clock(clock::Response), + } + + #[derive(Copy, Clone, Debug)] + pub struct IrohService; + impl quic_rpc::Service for IrohService { + type Req = Request; + type Res = Response; + } + + #[derive(Clone, Default)] + pub struct Handler { + calc: calc::Handler, + clock: clock::Handler, + } + + impl Handler { + pub async fn handle_rpc_request( + self, + req: Request, + chan: RpcChannel, + ) -> Result<()> + where + S: IntoService, + E: ServiceEndpoint, + { + match req { + Request::Calc(req) => self.calc.handle_rpc_request(req, chan.map()).await?, + Request::Clock(req) => self.clock.handle_rpc_request(req, chan.map()).await?, + } + Ok(()) + } + } + + #[derive(Debug, Clone)] + pub struct Client { + pub calc: calc::Client, + pub clock: clock::Client, + } + + impl Client + where + C: ServiceConnection, + S: IntoService, + { + pub fn new(conn: C) -> Self { + let client = RpcClient::new(conn); + Self { + calc: calc::Client::new(client.clone()), + clock: clock::Client::new(client.clone()), + } + } + } +} + mod calc { + use anyhow::Result; use derive_more::{From, TryInto}; use quic_rpc::{ message::RpcMsg, server::RpcChannel, IntoService, RpcClient, ServiceConnection, @@ -157,13 +267,15 @@ mod calc { self, req: Request, chan: RpcChannel, - ) where + ) -> Result<()> + where S: IntoService, E: ServiceEndpoint, { - let _res = match req { - Request::Add(req) => chan.rpc(req, self, Self::on_add).await, - }; + match req { + Request::Add(req) => chan.rpc(req, self, Self::on_add).await?, + } + Ok(()) } pub async fn on_add(self, req: AddRequest) -> AddResponse { @@ -276,13 +388,15 @@ mod clock { self, req: Request, chan: RpcChannel, - ) where + ) -> Result<()> + where S: IntoService, E: ServiceEndpoint, { - let _res = match req { - Request::Tick(req) => chan.server_streaming(req, self, Self::on_tick).await, - }; + match req { + Request::Tick(req) => chan.server_streaming(req, self, Self::on_tick).await?, + } + Ok(()) } pub fn on_tick( diff --git a/src/client.rs b/src/client.rs index 0c21d15..60f057e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -65,7 +65,7 @@ where } fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { - let req = S::outer_req_from(item); + let req = S::req_up(item); self.project().0.start_send_unpin(req) } @@ -112,7 +112,7 @@ impl, C: ServiceConnection, S2: Service> RpcClie where M: RpcMsg, { - let msg = S::outer_req_from(msg); + let msg = S::req_up(msg); let (mut send, mut recv) = self.source.open_bi().await.map_err(RpcClientError::Open)?; send.send(msg).await.map_err(RpcClientError::::Send)?; let res = recv @@ -122,7 +122,7 @@ impl, C: ServiceConnection, S2: Service> RpcClie .map_err(RpcClientError::::RecvError)?; // keep send alive until we have the answer drop(send); - let res = S::try_inner_res_from(res).map_err(|_| RpcClientError::DowncastError)?; + let res = S::try_res_down(res).map_err(|_| RpcClientError::DowncastError)?; M::Response::try_from(res).map_err(|_| RpcClientError::DowncastError) } @@ -137,7 +137,7 @@ impl, C: ServiceConnection, S2: Service> RpcClie where M: ServerStreamingMsg, { - let msg = S::outer_req_from(msg); + let msg = S::req_up(msg); let (mut send, recv) = self .source .open_bi() @@ -148,7 +148,7 @@ impl, C: ServiceConnection, S2: Service> RpcClie .await?; let recv = recv.map(move |x| match x { Ok(x) => { - let x = S::try_inner_res_from(x) + let x = S::try_res_down(x) .map_err(|_| StreamingResponseItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| StreamingResponseItemError::DowncastError) } @@ -173,7 +173,7 @@ impl, C: ServiceConnection, S2: Service> RpcClie where M: ClientStreamingMsg, { - let msg = S::outer_req_from(msg); + let msg = S::req_up(msg); let (mut send, mut recv) = self .source .open_bi() @@ -189,7 +189,7 @@ impl, C: ServiceConnection, S2: Service> RpcClie match item { Ok(x) => { - let x = S::try_inner_res_from(x) + let x = S::try_res_down(x) .map_err(|_| ClientStreamingItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| ClientStreamingItemError::DowncastError) } @@ -214,14 +214,14 @@ impl, C: ServiceConnection, S2: Service> RpcClie where M: BidiStreamingMsg, { - let msg = S::outer_req_from(msg); + let msg = S::req_up(msg); let (mut send, recv) = self.source.open_bi().await.map_err(BidiError::Open)?; send.send(msg).await.map_err(BidiError::::Send)?; let send = UpdateSink(send, PhantomData, PhantomData); let recv = recv .map(|x| match x { Ok(x) => { - let x = S::try_inner_res_from(x).map_err(|_| BidiItemError::DowncastError)?; + let x = S::try_res_down(x).map_err(|_| BidiItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError) } Err(e) => Err(BidiItemError::RecvError(e)), diff --git a/src/lib.rs b/src/lib.rs index 11b9b12..c5a1a13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -168,13 +168,13 @@ pub trait Service: Send + Sync + Debug + Clone + 'static { /// If an outer service impls [`IntoService`] for an inner service, the [`crate::RpcChannel`] and [`RpcClient`] pub trait IntoService: Service { /// Convert the inner request into the outer request. - fn outer_req_from(req: impl Into) -> Self::Req; + fn req_up(req: impl Into) -> Self::Req; /// Convert the inner response into the outer response. - fn outer_res_from(res: impl Into) -> Self::Res; + fn res_up(res: impl Into) -> Self::Res; /// Try to convert the outer request into the inner request. - fn try_inner_req_from(req: Self::Req) -> Result; + fn try_req_down(req: Self::Req) -> Result; /// Try to convert the outer response into the inner response. - fn try_inner_res_from(res: Self::Res) -> Result; + fn try_res_down(res: Self::Res) -> Result; } impl IntoService for S0 @@ -184,19 +184,19 @@ where S2::Req: Into + TryFrom + Send + 'static, S2::Res: Into + TryFrom + Send + 'static, { - fn outer_req_from(req: impl Into) -> S0::Req { + fn req_up(req: impl Into) -> S0::Req { (req.into()).into() } - fn outer_res_from(res: impl Into) -> S0::Res { + fn res_up(res: impl Into) -> S0::Res { (res.into()).into() } - fn try_inner_req_from(req: Self::Req) -> Result { + fn try_req_down(req: Self::Req) -> Result { req.try_into().map_err(|_| ()) } - fn try_inner_res_from(res: Self::Res) -> Result { + fn try_res_down(res: Self::Res) -> Result { res.try_into().map_err(|_| ()) } } diff --git a/src/server.rs b/src/server.rs index ac496e2..4437a7a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -113,7 +113,7 @@ impl, C: ServiceEndpoint, S2: Service> RpcChannel, C: ServiceEndpoint, S2: Service> RpcChannel, C: ServiceEndpoint, S2: Service> RpcChannel, C: ServiceEndpoint, S2: Service> RpcChannel Date: Fri, 8 Mar 2024 14:56:40 +0200 Subject: [PATCH 18/49] increase version number --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab64180..146a4fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -701,7 +701,7 @@ dependencies = [ [[package]] name = "quic-rpc" -version = "0.6.2" +version = "0.6.3" dependencies = [ "anyhow", "async-stream", diff --git a/Cargo.toml b/Cargo.toml index cccdc6a..6e80430 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quic-rpc" -version = "0.6.2" +version = "0.6.3" edition = "2021" authors = ["Rüdiger Klaehn "] keywords = ["api", "protocol", "network", "rpc"] From a7afa10f105e2be8e91fb8ded65a920d270d26f5 Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Fri, 8 Mar 2024 14:06:04 +0100 Subject: [PATCH 19/49] compiler errors --- src/client.rs | 256 +++++++++++++++++++++++++++++++++++++++++--------- src/lib.rs | 53 +---------- src/server.rs | 204 +++++++++++++++++++++++++++++++++++----- 3 files changed, 393 insertions(+), 120 deletions(-) diff --git a/src/client.rs b/src/client.rs index 60f057e..e7514c5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,7 +4,7 @@ use crate::{ message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, transport::ConnectionErrors, - IntoService, Service, ServiceConnection, + Service, ServiceConnection, }; use futures::{ future::BoxFuture, stream::BoxStream, FutureExt, Sink, SinkExt, Stream, StreamExt, TryFutureExt, @@ -16,6 +16,7 @@ use std::{ marker::PhantomData, pin::Pin, result, + sync::Arc, task::{Context, Poll}, }; @@ -24,18 +25,16 @@ use std::{ /// This is a wrapper around a [ServiceConnection] that serves as the entry point /// for the client DSL. `S` is the service type, `C` is the substream source. #[derive(Debug)] -pub struct RpcClient { +pub struct RpcClient { source: C, - p: PhantomData, - p2: PhantomData, + map: Arc>, } impl Clone for RpcClient { fn clone(&self) -> Self { Self { source: self.source.clone(), - p: PhantomData, - p2: PhantomData, + map: Arc::clone(&self.map), } } } @@ -44,19 +43,19 @@ impl Clone for RpcClient { /// that support it, [crate::message::ClientStreaming] and [crate::message::BidiStreaming]. #[pin_project] #[derive(Debug)] -pub struct UpdateSink(#[pin] C::SendSink, PhantomData, PhantomData) +pub struct UpdateSink(#[pin] C::SendSink, Arc>) where - S: IntoService, - S2: Service, + S: Service, + Sd: Service, C: ServiceConnection, - T: Into; + T: Into; -impl Sink for UpdateSink +impl Sink for UpdateSink where - S: IntoService, - S2: Service, + S: Service, + Sd: Service, C: ServiceConnection, - T: Into, + T: Into, { type Error = C::SendError; @@ -65,7 +64,7 @@ where } fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { - let req = S::req_up(item); + let req = self.map.req_up(item); self.project().0.start_send_unpin(req) } @@ -78,7 +77,11 @@ where } } -impl, C: ServiceConnection, S2: Service> RpcClient { +impl RpcClient +where + S: Service, + C: ServiceConnection, +{ /// Create a new rpc client for a specific [Service] given a compatible /// [ServiceConnection]. /// @@ -86,11 +89,17 @@ impl, C: ServiceConnection, S2: Service> RpcClie pub fn new(source: C) -> Self { Self { source, - p: PhantomData, - p2: PhantomData, + map: Arc::new(Mapper::new()), } } +} +impl RpcClient +where + S: Service, + C: ServiceConnection, + Sd: Service, +{ /// Get the underlying connection pub fn into_inner(self) -> C { self.source @@ -100,30 +109,45 @@ impl, C: ServiceConnection, S2: Service> RpcClie /// /// This method is available as long as the outer service implements [`IntoService`] for the /// inner service. - pub fn map(self) -> RpcClient + pub fn map(self) -> RpcClient where - S: IntoService, + Sd2: Service, + Sd2::Req: Into + TryFrom, + Sd2::Res: Into + TryFrom, { - RpcClient::::new(self.source) + let mapper = Mapper::::new(); + let map = ChainedMapper::new(self.map, mapper); + RpcClient { + source: self.source, + map: Arc::new(map), + } } /// RPC call to the server, single request, single response pub async fn rpc(&self, msg: M) -> result::Result> where - M: RpcMsg, + M: RpcMsg, { - let msg = S::req_up(msg); - let (mut send, mut recv) = self.source.open_bi().await.map_err(RpcClientError::Open)?; - send.send(msg).await.map_err(RpcClientError::::Send)?; - let res = recv - .next() - .await - .ok_or(RpcClientError::::EarlyClose)? - .map_err(RpcClientError::::RecvError)?; - // keep send alive until we have the answer - drop(send); - let res = S::try_res_down(res).map_err(|_| RpcClientError::DowncastError)?; - M::Response::try_from(res).map_err(|_| RpcClientError::DowncastError) + let msg: Sd::Req = msg.into(); + let msg = self.map.req_up(msg); + todo!() + // let msg: Sd::Req = msg.into(); + // let msg = self.map.req_up(msg); + // let (mut send, mut recv) = self.source.open_bi().await.map_err(RpcClientError::Open)?; + // send.send(msg).await.map_err(RpcClientError::::Send)?; + // let res = recv + // .next() + // .await + // .ok_or(RpcClientError::::EarlyClose)? + // .map_err(RpcClientError::::RecvError)?; + // // keep send alive until we have the answer + // drop(send); + // let res: S::Res = res; + // let res: Sd::Res = self + // .map + // .try_res_down(res) + // .map_err(|_| RpcClientError::DowncastError)?; + // M::Response::try_from(res).map_err(|_| RpcClientError::DowncastError) } /// Bidi call to the server, request opens a stream, response is a stream @@ -135,9 +159,9 @@ impl, C: ServiceConnection, S2: Service> RpcClie StreamingResponseError, > where - M: ServerStreamingMsg, + M: ServerStreamingMsg, { - let msg = S::req_up(msg); + let msg = self.map.req_up(msg); let (mut send, recv) = self .source .open_bi() @@ -148,7 +172,9 @@ impl, C: ServiceConnection, S2: Service> RpcClie .await?; let recv = recv.map(move |x| match x { Ok(x) => { - let x = S::try_res_down(x) + let x = self + .map + .try_res_down(x) .map_err(|_| StreamingResponseItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| StreamingResponseItemError::DowncastError) } @@ -165,22 +191,22 @@ impl, C: ServiceConnection, S2: Service> RpcClie msg: M, ) -> result::Result< ( - UpdateSink, + UpdateSink, BoxFuture<'static, result::Result>>, ), ClientStreamingError, > where - M: ClientStreamingMsg, + M: ClientStreamingMsg, { - let msg = S::req_up(msg); + let msg = self.map.req_up(msg); let (mut send, mut recv) = self .source .open_bi() .await .map_err(ClientStreamingError::Open)?; send.send(msg).map_err(ClientStreamingError::Send).await?; - let send = UpdateSink::(send, PhantomData, PhantomData); + let send = UpdateSink::(send, Arc::clone(&self.map)); let recv = async move { let item = recv .next() @@ -189,7 +215,9 @@ impl, C: ServiceConnection, S2: Service> RpcClie match item { Ok(x) => { - let x = S::try_res_down(x) + let x = self + .map + .try_res_down(x) .map_err(|_| ClientStreamingItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| ClientStreamingItemError::DowncastError) } @@ -206,22 +234,25 @@ impl, C: ServiceConnection, S2: Service> RpcClie msg: M, ) -> result::Result< ( - UpdateSink, + UpdateSink, BoxStream<'static, result::Result>>, ), BidiError, > where - M: BidiStreamingMsg, + M: BidiStreamingMsg, { - let msg = S::req_up(msg); + let msg = self.map.req_up(msg); let (mut send, recv) = self.source.open_bi().await.map_err(BidiError::Open)?; send.send(msg).await.map_err(BidiError::::Send)?; - let send = UpdateSink(send, PhantomData, PhantomData); + let send = UpdateSink(send, Arc::clone(&self.map)); let recv = recv .map(|x| match x { Ok(x) => { - let x = S::try_res_down(x).map_err(|_| BidiItemError::DowncastError)?; + let x = self + .map + .try_res_down(x) + .map_err(|_| BidiItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError) } Err(e) => Err(BidiItemError::RecvError(e)), @@ -375,3 +406,134 @@ impl Stream for DeferDrop { self.project().0.poll_next(cx) } } + +trait IntoService: 'static { + type Up: Service; + type Down: Service; + fn req_up(&self, req: impl Into<::Req>) -> ::Req; + fn res_up(&self, res: impl Into<::Res>) -> ::Res; + fn try_req_down( + &self, + req: ::Req, + ) -> Result<::Req, ()>; + fn try_res_down( + &self, + res: ::Res, + ) -> Result<::Res, ()>; +} + +struct Mapper { + s1: PhantomData, + s2: PhantomData, +} + +impl Mapper +where + S1: Service, + S2: Service, + S2::Req: Into, + S2::Res: Into, +{ + pub fn new() -> Self { + Self { + s1: PhantomData, + s2: PhantomData, + } + } +} + +impl IntoService for Mapper +where + S1: Service, + S2: Service, + S2::Req: Into + TryFrom, + S2::Res: Into + TryFrom, +{ + type Up = S1; + type Down = S2; + + fn req_up(&self, req: impl Into<::Req>) -> ::Req { + (req.into()).into() + } + + fn res_up(&self, res: impl Into<::Res>) -> ::Res { + (res.into()).into() + } + + fn try_req_down( + &self, + req: ::Req, + ) -> Result<::Req, ()> { + req.try_into().map_err(|_| ()) + } + + fn try_res_down( + &self, + res: ::Res, + ) -> Result<::Res, ()> { + res.try_into().map_err(|_| ()) + } +} + +struct ChainedMapper +where + S1: Service, + S2: Service, + M2: IntoService, + ::Req: Into<::Req>, +{ + m1: Arc>, + m2: M2, +} + +impl ChainedMapper +where + S1: Service, + S2: Service, + M2: IntoService, + ::Req: Into<::Req>, +{ + /// + pub fn new(m1: Arc>, m2: M2) -> Self { + Self { m1, m2 } + } +} + +impl IntoService for ChainedMapper +where + S1: Service, + S2: Service, + M2: IntoService, + ::Req: Into<::Req>, + ::Res: Into<::Res>, +{ + type Up = S1; + type Down = ::Down; + fn req_up(&self, req: ::Req) -> ::Req { + let req = self.m2.req_up(req); + let req = self.m1.req_up(req.into()); + req + } + fn res_up(&self, res: ::Res) -> ::Res { + let res = self.m2.res_up(res); + let res = self.m1.res_up(res.into()); + res + } + fn try_req_down( + &self, + req: ::Req, + ) -> Result<::Req, ()> { + let req = self.m2.try_req_down(req); + let req = self.m1.try_req_down(req.into()); + req + } + + fn try_res_down( + &self, + res: ::Res, + ) -> Result<::Res, ()> { + let res = self.m2.try_res_down(res); + let res = self.m1.try_res_down(res.into()); + res + } +} diff --git a/src/lib.rs b/src/lib.rs index c5a1a13..6f46c13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,7 +92,11 @@ #![deny(missing_docs)] #![deny(rustdoc::broken_intra_doc_links)] use serde::{de::DeserializeOwned, Serialize}; -use std::fmt::{Debug, Display}; +use std::{ + fmt::{Debug, Display}, + marker::PhantomData, + sync::Arc, +}; use transport::{Connection, ServerEndpoint}; pub mod client; pub mod message; @@ -154,53 +158,6 @@ pub trait Service: Send + Sync + Debug + Clone + 'static { type Res: RpcMessage; } -/// Marker trait for services that can be mapped into another service. -/// -/// This can be used in place of the usual `S: Service` generic to accept any service that can be -/// mapped to the service in question. -/// -/// There is usually no need to impl this trait manually, because it is auto-implemented as long as -/// the inner service's Req and Res types implement [`Into`] and [`TryFrom`] to the outer service's types. -/// -/// See `examples/modularize.rs` for an example of how to use this trait to decouple RPC services -/// between different, independent modules or crates. -/// -/// If an outer service impls [`IntoService`] for an inner service, the [`crate::RpcChannel`] and [`RpcClient`] -pub trait IntoService: Service { - /// Convert the inner request into the outer request. - fn req_up(req: impl Into) -> Self::Req; - /// Convert the inner response into the outer response. - fn res_up(res: impl Into) -> Self::Res; - /// Try to convert the outer request into the inner request. - fn try_req_down(req: Self::Req) -> Result; - /// Try to convert the outer response into the inner response. - fn try_res_down(res: Self::Res) -> Result; -} - -impl IntoService for S0 -where - S0: Service, - S2: Service, - S2::Req: Into + TryFrom + Send + 'static, - S2::Res: Into + TryFrom + Send + 'static, -{ - fn req_up(req: impl Into) -> S0::Req { - (req.into()).into() - } - - fn res_up(res: impl Into) -> S0::Res { - (res.into()).into() - } - - fn try_req_down(req: Self::Req) -> Result { - req.try_into().map_err(|_| ()) - } - - fn try_res_down(res: Self::Res) -> Result { - res.try_into().map_err(|_| ()) - } -} - /// A connection to a specific service on a specific remote machine /// /// This is just a trait alias for a [Connection] with the right types. diff --git a/src/server.rs b/src/server.rs index 4437a7a..8b56f7e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,13 +2,18 @@ //! //! The main entry point is [RpcServer] use crate::{ - message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, - transport::ConnectionErrors, - IntoService, Service, ServiceEndpoint, + message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, transport::ConnectionErrors, Service, ServiceEndpoint }; use futures::{channel::oneshot, task, task::Poll, Future, FutureExt, SinkExt, Stream, StreamExt}; use pin_project::pin_project; -use std::{error, fmt, fmt::Debug, marker::PhantomData, pin::Pin, result}; +use std::{ + error, + fmt::{self, Debug}, + marker::PhantomData, + pin::Pin, + result, + sync::Arc, +}; /// A server channel for a specific service. /// @@ -54,36 +59,52 @@ impl> RpcServer { /// Sink and stream are independent, so you can take the channel apart and use /// them independently. #[derive(Debug)] -pub struct RpcChannel, C: ServiceEndpoint, S2: Service = S> { +pub struct RpcChannel, Sd: Service = S> { /// Sink to send responses to the client. pub send: C::SendSink, /// Stream to receive requests from the client. pub recv: C::RecvStream, - /// Phantom data to make the type parameter `S` non-instantiable. - p: PhantomData, - p2: PhantomData, + /// Mapper to map between S and S2 + map: Arc>, } -impl, C: ServiceEndpoint, S2: Service> RpcChannel { - /// Create a new channel from a sink and a stream. +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, +{ pub fn new(send: C::SendSink, recv: C::RecvStream) -> Self { Self { send, recv, - p: PhantomData, - p2: PhantomData, + map: Box::new(Mapper::new()), } } +} +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, + Sd: Service, +{ /// Map this channel's service into an inner service. /// /// This method is available as long as the outer service implements [`IntoService`] for the /// inner service. - pub fn map(self) -> RpcChannel + pub fn map(self) -> RpcChannel where - S: IntoService, + Sd2: Service, + Sd2::Req: Into + TryFrom, + Sd2::Res: Into + TryFrom, { - RpcChannel::::new(self.send, self.recv) + let mapper = Mapper::::new(); + let map = ChainedMapper::new(self.map, mapper); + RpcChannel { + send: self.send, + recv: Self.recv, + map: Arc::new(map), + } } /// handle the message of type `M` using the given function on the target object @@ -96,7 +117,7 @@ impl, C: ServiceEndpoint, S2: Service> RpcChannel result::Result<(), RpcServerError> where - M: RpcMsg, + M: RpcMsg, F: FnOnce(T, M) -> Fut, Fut: Future, T: Send + 'static, @@ -113,7 +134,7 @@ impl, C: ServiceEndpoint, S2: Service> RpcChannel, C: ServiceEndpoint, S2: Service> RpcChannel result::Result<(), RpcServerError> where - M: ClientStreamingMsg, + M: ClientStreamingMsg, F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, Fut: Future + Send + 'static, T: Send + 'static, @@ -141,7 +162,7 @@ impl, C: ServiceEndpoint, S2: Service> RpcChannel, C: ServiceEndpoint, S2: Service> RpcChannel result::Result<(), RpcServerError> where - M: BidiStreamingMsg, + M: BidiStreamingMsg, F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, Str: Stream + Send + 'static, T: Send + 'static, @@ -172,9 +193,11 @@ impl, C: ServiceEndpoint, S2: Service> RpcChannel, C: ServiceEndpoint, S2: Service> RpcChannel result::Result<(), RpcServerError> where - M: ServerStreamingMsg, + M: ServerStreamingMsg, F: FnOnce(T, M) -> Str + Send + 'static, Str: Stream + Send + 'static, T: Send + 'static, @@ -210,7 +233,7 @@ impl, C: ServiceEndpoint, S2: Service> RpcChannel, C: ServiceEndpoint, S2: Service> RpcChannel result::Result<(), RpcServerError> where - M: RpcMsg>, + M: RpcMsg>, F: FnOnce(T, M) -> Fut, Fut: Future>, E2: From, @@ -425,3 +448,134 @@ where handler(chan, req, target).await?; } } + +trait IntoService: 'static { + type Up: Service; + type Down: Service; + fn req_up(&self, req: impl Into<::Req>) -> ::Req; + fn res_up(&self, res: impl Into<::Res>) -> ::Res; + fn try_req_down( + &self, + req: ::Req, + ) -> Result<::Req, ()>; + fn try_res_down( + &self, + res: ::Res, + ) -> Result<::Res, ()>; +} + +struct Mapper { + s1: PhantomData, + s2: PhantomData, +} + +impl Mapper +where + S1: Service, + S2: Service, + S2::Req: Into, + S2::Res: Into, +{ + pub fn new() -> Self { + Self { + s1: PhantomData, + s2: PhantomData, + } + } +} + +impl IntoService for Mapper +where + S1: Service, + S2: Service, + S2::Req: Into + TryFrom, + S2::Res: Into + TryFrom, +{ + type Up = S1; + type Down = S2; + + fn req_up(&self, req: impl Into<::Req>) -> ::Req { + (req.into()).into() + } + + fn res_up(&self, res: impl Into<::Res>) -> ::Res { + (res.into()).into() + } + + fn try_req_down( + &self, + req: ::Req, + ) -> Result<::Req, ()> { + req.try_into().map_err(|_| ()) + } + + fn try_res_down( + &self, + res: ::Res, + ) -> Result<::Res, ()> { + res.try_into().map_err(|_| ()) + } +} + +struct ChainedMapper +where + S1: Service, + S2: Service, + M2: IntoService, + ::Req: Into<::Req>, +{ + m1: Arc>, + m2: M2, +} + +impl ChainedMapper +where + S1: Service, + S2: Service, + M2: IntoService, + ::Req: Into<::Req>, +{ + /// + pub fn new(m1: Arc>, m2: M2) -> Self { + Self { m1, m2 } + } +} + +impl IntoService for ChainedMapper +where + S1: Service, + S2: Service, + M2: IntoService, + ::Req: Into<::Req>, + ::Res: Into<::Res>, +{ + type Up = S1; + type Down = ::Down; + fn req_up(&self, req: ::Req) -> ::Req { + let req = self.m2.req_up(req); + let req = self.m1.req_up(req.into()); + req + } + fn res_up(&self, res: ::Res) -> ::Res { + let res = self.m2.res_up(res); + let res = self.m1.res_up(res.into()); + res + } + fn try_req_down( + &self, + req: ::Req, + ) -> Result<::Req, ()> { + let req = self.m2.try_req_down(req); + let req = self.m1.try_req_down(req.into()); + req + } + + fn try_res_down( + &self, + res: ::Res, + ) -> Result<::Res, ()> { + let res = self.m2.try_res_down(res); + let res = self.m1.try_res_down(res.into()); + res + } +} From f5a835922fd7539f7f7e3b951ecd040cac36d1b6 Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Fri, 8 Mar 2024 15:36:24 +0100 Subject: [PATCH 20/49] make things work --- examples/modularize.rs | 101 ++++++++++------- src/client.rs | 246 ++++++++++------------------------------- src/lib.rs | 7 +- src/map.rs | 114 +++++++++++++++++++ src/server.rs | 180 +++++------------------------- 5 files changed, 261 insertions(+), 387 deletions(-) create mode 100644 src/map.rs diff --git a/examples/modularize.rs b/examples/modularize.rs index bb73516..3ecc80a 100644 --- a/examples/modularize.rs +++ b/examples/modularize.rs @@ -14,10 +14,14 @@ use tracing::warn; #[tokio::main] async fn main() -> anyhow::Result<()> { + // Spawn an inmemory connection. + // Could use quic equally (all code in this example is generic over the transport) let (server_conn, client_conn) = flume::connection::(1); + // spawn the server tokio::task::spawn(async move { let server = RpcServer::new(server_conn); + // create our app handler which composes the other handlers let handler = app::Handler::default(); loop { match server.accept().await { @@ -25,7 +29,7 @@ async fn main() -> anyhow::Result<()> { Ok((req, chan)) => { let handler = handler.clone(); tokio::task::spawn(async move { - if let Err(err) = handler.handle_rpc_request(req, chan) { + if let Err(err) = handler.handle_rpc_request(req, chan).await { warn!(?err, "internal rpc error"); } }); @@ -34,6 +38,7 @@ async fn main() -> anyhow::Result<()> { } }); + // run a client app::client_demo(client_conn).await?; Ok(()) @@ -42,12 +47,16 @@ async fn main() -> anyhow::Result<()> { mod app { //! This is the app-specific code. //! - //! It uses all of iroh (calc + clock) plus an app-specific endpoint + //! It composes all of `iroh` (which internally composes two other modules) and adds an + //! application specific RPC. + //! + //! It could also easily compose services from other crates or internal modules. + use anyhow::Result; use derive_more::{From, TryInto}; use futures::StreamExt; use quic_rpc::{ - message::RpcMsg, server::RpcChannel, RpcClient, ServiceConnection, ServiceEndpoint, + message::RpcMsg, server::RpcChannel, RpcClient, Service, ServiceConnection, ServiceEndpoint, }; use serde::{Deserialize, Serialize}; @@ -82,12 +91,21 @@ mod app { type Res = Response; } - #[derive(Clone, Default)] + #[derive(Clone)] pub struct Handler { iroh: iroh::Handler, app_version: String, } + impl Default for Handler { + fn default() -> Self { + Self { + iroh: iroh::Handler::default(), + app_version: "v0.1-alpha".to_string(), + } + } + } + impl Handler { pub async fn handle_rpc_request>( self, @@ -101,22 +119,25 @@ mod app { Ok(()) } - pub async fn on_version(self, req: AppVersionRequest) -> AppVersionResponse { - AppVersionResponse(self.version.clone()) + pub async fn on_version(self, _req: AppVersionRequest) -> AppVersionResponse { + AppVersionResponse(self.app_version.clone()) } } #[derive(Debug, Clone)] - pub struct Client> { - pub iroh: iroh::Client, - client: RpcClient, + pub struct Client> { + pub iroh: iroh::Client, + client: RpcClient, } - impl> Client { - pub fn new(conn: C) -> Self { - let client = RpcClient::new(conn); + impl Client + where + S: Service, + C: ServiceConnection, + { + pub fn new(client: RpcClient) -> Self { Self { - iroh: iroh::Client::new(client.clone()), + iroh: iroh::Client::new(client.clone().map()), client, } } @@ -128,7 +149,8 @@ mod app { } pub async fn client_demo>(conn: C) -> Result<()> { - let client = Client::new(conn); + let client = RpcClient::::new(conn); + let client = Client::new(client); println!("app service: version"); let res = client.app_version().await?; println!("app service: version res {res:?}"); @@ -146,14 +168,13 @@ mod app { } mod iroh { - //! This module composes two sub-services + //! This module composes two sub-services. Think `iroh` crate which exposes services and + //! clients for iroh-bytes and iroh-gossip or so. + //! It uses only the `calc` and `clock` modules and nothing else. use anyhow::Result; use derive_more::{From, TryInto}; - use futures::StreamExt; - use quic_rpc::{ - server::RpcChannel, IntoService, RpcClient, ServiceConnection, ServiceEndpoint, - }; + use quic_rpc::{server::RpcChannel, RpcClient, Service, ServiceConnection, ServiceEndpoint}; use serde::{Deserialize, Serialize}; use super::{calc, clock}; @@ -190,7 +211,7 @@ mod iroh { chan: RpcChannel, ) -> Result<()> where - S: IntoService, + S: Service, E: ServiceEndpoint, { match req { @@ -210,24 +231,25 @@ mod iroh { impl Client where C: ServiceConnection, - S: IntoService, + S: Service, { - pub fn new(conn: C) -> Self { - let client = RpcClient::new(conn); + pub fn new(client: RpcClient) -> Self { Self { - calc: calc::Client::new(client.clone()), - clock: clock::Client::new(client.clone()), + calc: calc::Client::new(client.clone().map()), + clock: clock::Client::new(client.clone().map()), } } } } mod calc { + //! This is a library providing a service, and a client. E.g. iroh-bytes or iroh-hypermerge. + //! It does not use any `super` imports, it is completely decoupled. + use anyhow::Result; use derive_more::{From, TryInto}; use quic_rpc::{ - message::RpcMsg, server::RpcChannel, IntoService, RpcClient, ServiceConnection, - ServiceEndpoint, + message::RpcMsg, server::RpcChannel, RpcClient, Service, ServiceConnection, ServiceEndpoint, }; use serde::{Deserialize, Serialize}; use std::fmt::Debug; @@ -269,7 +291,7 @@ mod calc { chan: RpcChannel, ) -> Result<()> where - S: IntoService, + S: Service, E: ServiceEndpoint, { match req { @@ -291,12 +313,10 @@ mod calc { impl Client where C: ServiceConnection, - S: IntoService, + S: Service, { - pub fn new(client: RpcClient) -> Self { - Self { - client: client.map(), - } + pub fn new(client: RpcClient) -> Self { + Self { client } } pub async fn add(&self, a: i64, b: i64) -> anyhow::Result { let res = self.client.rpc(AddRequest(a, b)).await?; @@ -306,13 +326,16 @@ mod calc { } mod clock { + //! This is a library providing a service, and a client. E.g. iroh-bytes or iroh-hypermerge. + //! It does not use any `super` imports, it is completely decoupled. + use anyhow::Result; use derive_more::{From, TryInto}; use futures::{stream::BoxStream, Stream, StreamExt, TryStreamExt}; use quic_rpc::{ message::{Msg, ServerStreaming, ServerStreamingMsg}, server::RpcChannel, - IntoService, RpcClient, ServiceConnection, ServiceEndpoint, + RpcClient, Service, ServiceConnection, ServiceEndpoint, }; use serde::{Deserialize, Serialize}; use std::{ @@ -390,7 +413,7 @@ mod clock { chan: RpcChannel, ) -> Result<()> where - S: IntoService, + S: Service, E: ServiceEndpoint, { match req { @@ -433,12 +456,10 @@ mod clock { impl Client where C: ServiceConnection, - S: IntoService, + S: Service, { - pub fn new(client: RpcClient) -> Self { - Self { - client: client.map(), - } + pub fn new(client: RpcClient) -> Self { + Self { client } } pub async fn tick(&self) -> Result>> { let res = self.client.server_streaming(TickRequest).await?; diff --git a/src/client.rs b/src/client.rs index e7514c5..40c7cdd 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,6 +2,7 @@ //! //! The main entry point is [RpcClient]. use crate::{ + map::{ChainedMapper, IntoService, Mapper}, message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, transport::ConnectionErrors, Service, ServiceConnection, @@ -25,9 +26,9 @@ use std::{ /// This is a wrapper around a [ServiceConnection] that serves as the entry point /// for the client DSL. `S` is the service type, `C` is the substream source. #[derive(Debug)] -pub struct RpcClient { +pub struct RpcClient { source: C, - map: Arc>, + map: Arc>, } impl Clone for RpcClient { @@ -43,19 +44,23 @@ impl Clone for RpcClient { /// that support it, [crate::message::ClientStreaming] and [crate::message::BidiStreaming]. #[pin_project] #[derive(Debug)] -pub struct UpdateSink(#[pin] C::SendSink, Arc>) +pub struct UpdateSink( + #[pin] C::SendSink, + PhantomData, + Arc>, +) where S: Service, - Sd: Service, + S2: Service, C: ServiceConnection, - T: Into; + T: Into; -impl Sink for UpdateSink +impl Sink for UpdateSink where S: Service, - Sd: Service, + S2: Service, C: ServiceConnection, - T: Into, + T: Into, { type Error = C::SendError; @@ -64,7 +69,7 @@ where } fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { - let req = self.map.req_up(item); + let req = self.2.req_up(item.into()); self.project().0.start_send_unpin(req) } @@ -77,7 +82,7 @@ where } } -impl RpcClient +impl RpcClient where S: Service, C: ServiceConnection, @@ -94,11 +99,11 @@ where } } -impl RpcClient +impl RpcClient where S: Service, C: ServiceConnection, - Sd: Service, + S2: Service, { /// Get the underlying connection pub fn into_inner(self) -> C { @@ -109,14 +114,13 @@ where /// /// This method is available as long as the outer service implements [`IntoService`] for the /// inner service. - pub fn map(self) -> RpcClient + pub fn map(self) -> RpcClient where - Sd2: Service, - Sd2::Req: Into + TryFrom, - Sd2::Res: Into + TryFrom, + S3: Service, + S3::Req: Into + TryFrom, + S3::Res: Into + TryFrom, { - let mapper = Mapper::::new(); - let map = ChainedMapper::new(self.map, mapper); + let map = ChainedMapper::::new(self.map); RpcClient { source: self.source, map: Arc::new(map), @@ -126,28 +130,25 @@ where /// RPC call to the server, single request, single response pub async fn rpc(&self, msg: M) -> result::Result> where - M: RpcMsg, + M: RpcMsg, { - let msg: Sd::Req = msg.into(); - let msg = self.map.req_up(msg); - todo!() - // let msg: Sd::Req = msg.into(); - // let msg = self.map.req_up(msg); - // let (mut send, mut recv) = self.source.open_bi().await.map_err(RpcClientError::Open)?; - // send.send(msg).await.map_err(RpcClientError::::Send)?; - // let res = recv - // .next() - // .await - // .ok_or(RpcClientError::::EarlyClose)? - // .map_err(RpcClientError::::RecvError)?; - // // keep send alive until we have the answer - // drop(send); - // let res: S::Res = res; - // let res: Sd::Res = self - // .map - // .try_res_down(res) - // .map_err(|_| RpcClientError::DowncastError)?; - // M::Response::try_from(res).map_err(|_| RpcClientError::DowncastError) + let msg = self.map.req_up(msg.into()); + let (mut send, mut recv) = self.source.open_bi().await.map_err(RpcClientError::Open)?; + send.send(msg.into()) + .await + .map_err(RpcClientError::::Send)?; + let res = recv + .next() + .await + .ok_or(RpcClientError::::EarlyClose)? + .map_err(RpcClientError::::RecvError)?; + // keep send alive until we have the answer + drop(send); + let res: S2::Res = self + .map + .try_res_down(res) + .map_err(|_| RpcClientError::DowncastError)?; + M::Response::try_from(res).map_err(|_| RpcClientError::DowncastError) } /// Bidi call to the server, request opens a stream, response is a stream @@ -159,9 +160,9 @@ where StreamingResponseError, > where - M: ServerStreamingMsg, + M: ServerStreamingMsg, { - let msg = self.map.req_up(msg); + let msg = self.map.req_up(msg.into()); let (mut send, recv) = self .source .open_bi() @@ -170,10 +171,10 @@ where send.send(msg) .map_err(StreamingResponseError::::Send) .await?; + let map = Arc::clone(&self.map); let recv = recv.map(move |x| match x { Ok(x) => { - let x = self - .map + let x = map .try_res_down(x) .map_err(|_| StreamingResponseItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| StreamingResponseItemError::DowncastError) @@ -191,22 +192,23 @@ where msg: M, ) -> result::Result< ( - UpdateSink, + UpdateSink, BoxFuture<'static, result::Result>>, ), ClientStreamingError, > where - M: ClientStreamingMsg, + M: ClientStreamingMsg, { - let msg = self.map.req_up(msg); + let msg = self.map.req_up(msg.into()); let (mut send, mut recv) = self .source .open_bi() .await .map_err(ClientStreamingError::Open)?; send.send(msg).map_err(ClientStreamingError::Send).await?; - let send = UpdateSink::(send, Arc::clone(&self.map)); + let send = UpdateSink::(send, PhantomData, Arc::clone(&self.map)); + let map = Arc::clone(&self.map); let recv = async move { let item = recv .next() @@ -215,8 +217,7 @@ where match item { Ok(x) => { - let x = self - .map + let x = map .try_res_down(x) .map_err(|_| ClientStreamingItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| ClientStreamingItemError::DowncastError) @@ -234,23 +235,23 @@ where msg: M, ) -> result::Result< ( - UpdateSink, + UpdateSink, BoxStream<'static, result::Result>>, ), BidiError, > where - M: BidiStreamingMsg, + M: BidiStreamingMsg, { - let msg = self.map.req_up(msg); + let msg = self.map.req_up(msg.into()); let (mut send, recv) = self.source.open_bi().await.map_err(BidiError::Open)?; send.send(msg).await.map_err(BidiError::::Send)?; - let send = UpdateSink(send, Arc::clone(&self.map)); + let send = UpdateSink(send, PhantomData, Arc::clone(&self.map)); + let map = Arc::clone(&self.map); let recv = recv - .map(|x| match x { + .map(move |x| match x { Ok(x) => { - let x = self - .map + let x = map .try_res_down(x) .map_err(|_| BidiItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError) @@ -262,7 +263,7 @@ where } } -impl> AsRef for RpcClient { +impl, S2: Service> AsRef for RpcClient { fn as_ref(&self) -> &C { &self.source } @@ -406,134 +407,3 @@ impl Stream for DeferDrop { self.project().0.poll_next(cx) } } - -trait IntoService: 'static { - type Up: Service; - type Down: Service; - fn req_up(&self, req: impl Into<::Req>) -> ::Req; - fn res_up(&self, res: impl Into<::Res>) -> ::Res; - fn try_req_down( - &self, - req: ::Req, - ) -> Result<::Req, ()>; - fn try_res_down( - &self, - res: ::Res, - ) -> Result<::Res, ()>; -} - -struct Mapper { - s1: PhantomData, - s2: PhantomData, -} - -impl Mapper -where - S1: Service, - S2: Service, - S2::Req: Into, - S2::Res: Into, -{ - pub fn new() -> Self { - Self { - s1: PhantomData, - s2: PhantomData, - } - } -} - -impl IntoService for Mapper -where - S1: Service, - S2: Service, - S2::Req: Into + TryFrom, - S2::Res: Into + TryFrom, -{ - type Up = S1; - type Down = S2; - - fn req_up(&self, req: impl Into<::Req>) -> ::Req { - (req.into()).into() - } - - fn res_up(&self, res: impl Into<::Res>) -> ::Res { - (res.into()).into() - } - - fn try_req_down( - &self, - req: ::Req, - ) -> Result<::Req, ()> { - req.try_into().map_err(|_| ()) - } - - fn try_res_down( - &self, - res: ::Res, - ) -> Result<::Res, ()> { - res.try_into().map_err(|_| ()) - } -} - -struct ChainedMapper -where - S1: Service, - S2: Service, - M2: IntoService, - ::Req: Into<::Req>, -{ - m1: Arc>, - m2: M2, -} - -impl ChainedMapper -where - S1: Service, - S2: Service, - M2: IntoService, - ::Req: Into<::Req>, -{ - /// - pub fn new(m1: Arc>, m2: M2) -> Self { - Self { m1, m2 } - } -} - -impl IntoService for ChainedMapper -where - S1: Service, - S2: Service, - M2: IntoService, - ::Req: Into<::Req>, - ::Res: Into<::Res>, -{ - type Up = S1; - type Down = ::Down; - fn req_up(&self, req: ::Req) -> ::Req { - let req = self.m2.req_up(req); - let req = self.m1.req_up(req.into()); - req - } - fn res_up(&self, res: ::Res) -> ::Res { - let res = self.m2.res_up(res); - let res = self.m1.res_up(res.into()); - res - } - fn try_req_down( - &self, - req: ::Req, - ) -> Result<::Req, ()> { - let req = self.m2.try_req_down(req); - let req = self.m1.try_req_down(req.into()); - req - } - - fn try_res_down( - &self, - res: ::Res, - ) -> Result<::Res, ()> { - let res = self.m2.try_res_down(res); - let res = self.m1.try_res_down(res.into()); - res - } -} diff --git a/src/lib.rs b/src/lib.rs index 6f46c13..8c6464d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,11 +92,7 @@ #![deny(missing_docs)] #![deny(rustdoc::broken_intra_doc_links)] use serde::{de::DeserializeOwned, Serialize}; -use std::{ - fmt::{Debug, Display}, - marker::PhantomData, - sync::Arc, -}; +use std::fmt::{Debug, Display}; use transport::{Connection, ServerEndpoint}; pub mod client; pub mod message; @@ -106,6 +102,7 @@ pub use client::RpcClient; pub use server::RpcServer; #[cfg(feature = "macros")] mod macros; +mod map; /// Requirements for a RPC message /// diff --git a/src/map.rs b/src/map.rs new file mode 100644 index 0000000..a305b3f --- /dev/null +++ b/src/map.rs @@ -0,0 +1,114 @@ +use std::{marker::PhantomData, sync::Arc}; + +use crate::Service; + +pub trait IntoService: std::fmt::Debug + Send + Sync + 'static { + fn req_up(&self, req: ::Req) -> ::Req; + fn res_up(&self, res: ::Res) -> ::Res; + fn try_req_down(&self, req: ::Req) -> Result<::Req, ()>; + fn try_res_down(&self, res: ::Res) -> Result<::Res, ()>; +} + +#[derive(Debug)] +pub struct Mapper { + s1: PhantomData, + s2: PhantomData, +} + +impl Mapper +where + S1: Service, + S2: Service, + S2::Req: Into + TryFrom, + S2::Res: Into + TryFrom, +{ + pub fn new() -> Self { + Self { + s1: PhantomData, + s2: PhantomData, + } + } +} + +impl IntoService for Mapper +where + S1: Service, + S2: Service, + S2::Req: Into + TryFrom, + S2::Res: Into + TryFrom, +{ + fn req_up(&self, req: ::Req) -> ::Req { + (req.into()).into() + } + + fn res_up(&self, res: ::Res) -> ::Res { + (res.into()).into() + } + + fn try_req_down(&self, req: ::Req) -> Result<::Req, ()> { + req.try_into().map_err(|_| ()) + } + + fn try_res_down(&self, res: ::Res) -> Result<::Res, ()> { + res.try_into().map_err(|_| ()) + } +} + +#[derive(Debug)] +pub struct ChainedMapper +where + S1: Service, + S2: Service, + S3: Service, + S3::Req: Into + TryFrom, +{ + m1: Arc>, + m2: Mapper, +} + +impl ChainedMapper +where + S1: Service, + S2: Service, + S3: Service, + S3::Req: Into + TryFrom, + S3::Res: Into + TryFrom, +{ + pub fn new(m1: Arc>) -> Self { + Self { + m1, + m2: Mapper::new(), + } + } +} + +impl IntoService for ChainedMapper +where + S1: Service, + S2: Service, + S3: Service, + S3::Req: Into + TryFrom, + S3::Res: Into + TryFrom, +{ + fn req_up(&self, req: S3::Req) -> S1::Req { + let req = self.m2.req_up(req); + let req = self.m1.req_up(req.into()); + req + } + fn res_up(&self, res: S3::Res) -> S1::Res { + let res = self.m2.res_up(res); + let res = self.m1.res_up(res.into()); + res + } + fn try_req_down(&self, req: S1::Req) -> Result { + let req = self.m1.try_req_down(req)?; + let req = self.m2.try_req_down(req.try_into().map_err(|_| ())?); + req + } + + fn try_res_down(&self, res: ::Res) -> Result<::Res, ()> { + let res = self.m1.try_res_down(res)?; + let res = self.m2.try_res_down(res.try_into().map_err(|_| ())?); + res + } +} diff --git a/src/server.rs b/src/server.rs index 8b56f7e..03bb128 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,7 +2,10 @@ //! //! The main entry point is [RpcServer] use crate::{ - message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, transport::ConnectionErrors, Service, ServiceEndpoint + map::{ChainedMapper, IntoService, Mapper}, + message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, + transport::ConnectionErrors, + Service, ServiceEndpoint, }; use futures::{channel::oneshot, task, task::Poll, Future, FutureExt, SinkExt, Stream, StreamExt}; use pin_project::pin_project; @@ -59,50 +62,50 @@ impl> RpcServer { /// Sink and stream are independent, so you can take the channel apart and use /// them independently. #[derive(Debug)] -pub struct RpcChannel, Sd: Service = S> { +pub struct RpcChannel, S2: Service = S> { /// Sink to send responses to the client. pub send: C::SendSink, /// Stream to receive requests from the client. pub recv: C::RecvStream, /// Mapper to map between S and S2 - map: Arc>, + map: Arc>, } -impl RpcChannel +impl RpcChannel where S: Service, C: ServiceEndpoint, { + /// Create a new RPC channel. pub fn new(send: C::SendSink, recv: C::RecvStream) -> Self { Self { send, recv, - map: Box::new(Mapper::new()), + map: Arc::new(Mapper::new()), } } } -impl RpcChannel +impl RpcChannel where S: Service, C: ServiceEndpoint, - Sd: Service, + S2: Service, { /// Map this channel's service into an inner service. /// /// This method is available as long as the outer service implements [`IntoService`] for the /// inner service. - pub fn map(self) -> RpcChannel + pub fn map(self) -> RpcChannel where - Sd2: Service, - Sd2::Req: Into + TryFrom, - Sd2::Res: Into + TryFrom, + S3: Service, + S3::Req: Into + TryFrom, + S3::Res: Into + TryFrom, { - let mapper = Mapper::::new(); - let map = ChainedMapper::new(self.map, mapper); + let map = ChainedMapper::new(self.map); RpcChannel { send: self.send, - recv: Self.recv, + recv: self.recv, map: Arc::new(map), } } @@ -117,7 +120,7 @@ where f: F, ) -> result::Result<(), RpcServerError> where - M: RpcMsg, + M: RpcMsg, F: FnOnce(T, M) -> Fut, Fut: Future, T: Send + 'static, @@ -134,7 +137,7 @@ where // get the response let res = f(target, req).await; // turn into a S::Res so we can send it - let res = self.map.res_up(res); + let res = self.map.res_up(res.into()); // send it and return the error if any send.send(res).await.map_err(RpcServerError::SendError) }) @@ -151,7 +154,7 @@ where f: F, ) -> result::Result<(), RpcServerError> where - M: ClientStreamingMsg, + M: ClientStreamingMsg, F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, Fut: Future + Send + 'static, T: Send + 'static, @@ -162,7 +165,7 @@ where // get the response let res = f(target, req, updates).await; // turn into a S::Res so we can send it - let res = self.map.res_up(res); + let res = self.map.res_up(res.into()); // send it and return the error if any send.send(res).await.map_err(RpcServerError::SendError) }) @@ -179,7 +182,7 @@ where f: F, ) -> result::Result<(), RpcServerError> where - M: BidiStreamingMsg, + M: BidiStreamingMsg, F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, Str: Stream + Send + 'static, T: Send + 'static, @@ -193,7 +196,7 @@ where tokio::pin!(responses); while let Some(response) = responses.next().await { // turn into a S::Res so we can send it - let response = self.map.res_up(response); + let response = self.map.res_up(response.into()); // send it and return the error if any send.send(response) .await @@ -214,7 +217,7 @@ where f: F, ) -> result::Result<(), RpcServerError> where - M: ServerStreamingMsg, + M: ServerStreamingMsg, F: FnOnce(T, M) -> Str + Send + 'static, Str: Stream + Send + 'static, T: Send + 'static, @@ -233,7 +236,7 @@ where tokio::pin!(responses); while let Some(response) = responses.next().await { // turn into a S::Res so we can send it - let response = self.map.res_up(response); + let response = self.map.res_up(response.into()); // send it and return the error if any send.send(response) .await @@ -255,7 +258,7 @@ where f: F, ) -> result::Result<(), RpcServerError> where - M: RpcMsg>, + M: RpcMsg>, F: FnOnce(T, M) -> Fut, Fut: Future>, E2: From, @@ -448,134 +451,3 @@ where handler(chan, req, target).await?; } } - -trait IntoService: 'static { - type Up: Service; - type Down: Service; - fn req_up(&self, req: impl Into<::Req>) -> ::Req; - fn res_up(&self, res: impl Into<::Res>) -> ::Res; - fn try_req_down( - &self, - req: ::Req, - ) -> Result<::Req, ()>; - fn try_res_down( - &self, - res: ::Res, - ) -> Result<::Res, ()>; -} - -struct Mapper { - s1: PhantomData, - s2: PhantomData, -} - -impl Mapper -where - S1: Service, - S2: Service, - S2::Req: Into, - S2::Res: Into, -{ - pub fn new() -> Self { - Self { - s1: PhantomData, - s2: PhantomData, - } - } -} - -impl IntoService for Mapper -where - S1: Service, - S2: Service, - S2::Req: Into + TryFrom, - S2::Res: Into + TryFrom, -{ - type Up = S1; - type Down = S2; - - fn req_up(&self, req: impl Into<::Req>) -> ::Req { - (req.into()).into() - } - - fn res_up(&self, res: impl Into<::Res>) -> ::Res { - (res.into()).into() - } - - fn try_req_down( - &self, - req: ::Req, - ) -> Result<::Req, ()> { - req.try_into().map_err(|_| ()) - } - - fn try_res_down( - &self, - res: ::Res, - ) -> Result<::Res, ()> { - res.try_into().map_err(|_| ()) - } -} - -struct ChainedMapper -where - S1: Service, - S2: Service, - M2: IntoService, - ::Req: Into<::Req>, -{ - m1: Arc>, - m2: M2, -} - -impl ChainedMapper -where - S1: Service, - S2: Service, - M2: IntoService, - ::Req: Into<::Req>, -{ - /// - pub fn new(m1: Arc>, m2: M2) -> Self { - Self { m1, m2 } - } -} - -impl IntoService for ChainedMapper -where - S1: Service, - S2: Service, - M2: IntoService, - ::Req: Into<::Req>, - ::Res: Into<::Res>, -{ - type Up = S1; - type Down = ::Down; - fn req_up(&self, req: ::Req) -> ::Req { - let req = self.m2.req_up(req); - let req = self.m1.req_up(req.into()); - req - } - fn res_up(&self, res: ::Res) -> ::Res { - let res = self.m2.res_up(res); - let res = self.m1.res_up(res.into()); - res - } - fn try_req_down( - &self, - req: ::Req, - ) -> Result<::Req, ()> { - let req = self.m2.try_req_down(req); - let req = self.m1.try_req_down(req.into()); - req - } - - fn try_res_down( - &self, - res: ::Res, - ) -> Result<::Res, ()> { - let res = self.m2.try_res_down(res); - let res = self.m1.try_res_down(res.into()); - res - } -} From f3a7f0f0e583e0119a2dd973c4d54d9cb282fa5c Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Fri, 8 Mar 2024 15:46:26 +0100 Subject: [PATCH 21/49] cleanup --- src/client.rs | 17 +++++++++++------ src/map.rs | 26 +++++++++++++------------- src/server.rs | 13 +++++++++---- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/client.rs b/src/client.rs index 40c7cdd..f78bb18 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,7 +2,7 @@ //! //! The main entry point is [RpcClient]. use crate::{ - map::{ChainedMapper, IntoService, Mapper}, + map::{ChainedMapper, MapService, Mapper}, message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, transport::ConnectionErrors, Service, ServiceConnection, @@ -28,7 +28,7 @@ use std::{ #[derive(Debug)] pub struct RpcClient { source: C, - map: Arc>, + map: Arc>, } impl Clone for RpcClient { @@ -47,7 +47,7 @@ impl Clone for RpcClient { pub struct UpdateSink( #[pin] C::SendSink, PhantomData, - Arc>, + Arc>, ) where S: Service, @@ -112,15 +112,20 @@ where /// Map this channel's service into an inner service. /// - /// This method is available as long as the outer service implements [`IntoService`] for the - /// inner service. + /// This method is available if the required bounds are upheld: + /// S3::Req: Into + TryFrom, + /// S3::Res: Into + TryFrom, + /// + /// Where S3 is the new service to map to and S2 is the current inner service. + /// + /// This method can be chained infintely. pub fn map(self) -> RpcClient where S3: Service, S3::Req: Into + TryFrom, S3::Res: Into + TryFrom, { - let map = ChainedMapper::::new(self.map); + let map = ChainedMapper::new(self.map); RpcClient { source: self.source, map: Arc::new(map), diff --git a/src/map.rs b/src/map.rs index a305b3f..db6e716 100644 --- a/src/map.rs +++ b/src/map.rs @@ -2,11 +2,11 @@ use std::{marker::PhantomData, sync::Arc}; use crate::Service; -pub trait IntoService: std::fmt::Debug + Send + Sync + 'static { - fn req_up(&self, req: ::Req) -> ::Req; - fn res_up(&self, res: ::Res) -> ::Res; - fn try_req_down(&self, req: ::Req) -> Result<::Req, ()>; - fn try_res_down(&self, res: ::Res) -> Result<::Res, ()>; +pub trait MapService: std::fmt::Debug + Send + Sync + 'static { + fn req_up(&self, req: S2::Req) -> S1::Req; + fn res_up(&self, res: S2::Res) -> S1::Res; + fn try_req_down(&self, req: S1::Req) -> Result; + fn try_res_down(&self, res: S1::Res) -> Result; } #[derive(Debug)] @@ -30,26 +30,26 @@ where } } -impl IntoService for Mapper +impl MapService for Mapper where S1: Service, S2: Service, S2::Req: Into + TryFrom, S2::Res: Into + TryFrom, { - fn req_up(&self, req: ::Req) -> ::Req { + fn req_up(&self, req: S2::Req) -> S1::Req { (req.into()).into() } - fn res_up(&self, res: ::Res) -> ::Res { + fn res_up(&self, res: S2::Res) -> S1::Res { (res.into()).into() } - fn try_req_down(&self, req: ::Req) -> Result<::Req, ()> { + fn try_req_down(&self, req: S1::Req) -> Result { req.try_into().map_err(|_| ()) } - fn try_res_down(&self, res: ::Res) -> Result<::Res, ()> { + fn try_res_down(&self, res: S1::Res) -> Result { res.try_into().map_err(|_| ()) } } @@ -62,7 +62,7 @@ where S3: Service, S3::Req: Into + TryFrom, { - m1: Arc>, + m1: Arc>, m2: Mapper, } @@ -74,7 +74,7 @@ where S3::Req: Into + TryFrom, S3::Res: Into + TryFrom, { - pub fn new(m1: Arc>) -> Self { + pub fn new(m1: Arc>) -> Self { Self { m1, m2: Mapper::new(), @@ -82,7 +82,7 @@ where } } -impl IntoService for ChainedMapper +impl MapService for ChainedMapper where S1: Service, S2: Service, diff --git a/src/server.rs b/src/server.rs index 03bb128..2fec156 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,7 +2,7 @@ //! //! The main entry point is [RpcServer] use crate::{ - map::{ChainedMapper, IntoService, Mapper}, + map::{ChainedMapper, MapService, Mapper}, message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, transport::ConnectionErrors, Service, ServiceEndpoint, @@ -68,7 +68,7 @@ pub struct RpcChannel, S2: Service = S> { /// Stream to receive requests from the client. pub recv: C::RecvStream, /// Mapper to map between S and S2 - map: Arc>, + map: Arc>, } impl RpcChannel @@ -94,8 +94,13 @@ where { /// Map this channel's service into an inner service. /// - /// This method is available as long as the outer service implements [`IntoService`] for the - /// inner service. + /// This method is available if the required bounds are upheld: + /// S3::Req: Into + TryFrom, + /// S3::Res: Into + TryFrom, + /// + /// Where S3 is the new service to map to and S2 is the current inner service. + /// + /// This method can be chained infintely. pub fn map(self) -> RpcChannel where S3: Service, From a8d8396181da04ae8997ebc74d6b1c1880cbcf1c Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Fri, 8 Mar 2024 15:55:56 +0100 Subject: [PATCH 22/49] docs --- examples/modularize.rs | 10 ++++------ src/client.rs | 6 ++---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/examples/modularize.rs b/examples/modularize.rs index 3ecc80a..c17c0da 100644 --- a/examples/modularize.rs +++ b/examples/modularize.rs @@ -1,13 +1,11 @@ //! This example shows how an RPC service can be modularized, even between different crates. //! +//! * `app` module is the top level. it composes `iroh` plus one handler of the app itself +//! * `iroh` module composes two other services, `calc` and `clock` +//! //! The [`calc`] and [`clock`] modules both expose a [`quic_rpc::Service`] in a regular fashion. //! They do not `use` anything from `super` or `app` so they could live in their own crates -//! unchanged. The only difference to other examples is that their handlers take a generic -//! `S: IntoService`, which allows to pass in any service that can be mapped to -//! the module's service. -//! -//! The [`app`] module depends on both `calc` and `clock` and composes both their servers and -//! clients into a single app handler / client. +//! unchanged. use quic_rpc::{transport::flume, RpcServer}; use tracing::warn; diff --git a/src/client.rs b/src/client.rs index f78bb18..bb650eb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -139,9 +139,7 @@ where { let msg = self.map.req_up(msg.into()); let (mut send, mut recv) = self.source.open_bi().await.map_err(RpcClientError::Open)?; - send.send(msg.into()) - .await - .map_err(RpcClientError::::Send)?; + send.send(msg).await.map_err(RpcClientError::::Send)?; let res = recv .next() .await @@ -149,7 +147,7 @@ where .map_err(RpcClientError::::RecvError)?; // keep send alive until we have the answer drop(send); - let res: S2::Res = self + let res = self .map .try_res_down(res) .map_err(|_| RpcClientError::DowncastError)?; From f3469368c2e71d2ee9a34b5cd5dbbc44f45a46fc Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Sat, 9 Mar 2024 01:13:14 +0100 Subject: [PATCH 23/49] cleanups and better generic names --- examples/modularize.rs | 105 +++++++++++++++------------ src/client.rs | 77 +++++++++++--------- src/map.rs | 161 +++++++++++++++++++++++++---------------- src/server.rs | 40 +++++----- 4 files changed, 218 insertions(+), 165 deletions(-) diff --git a/examples/modularize.rs b/examples/modularize.rs index c17c0da..c2956f3 100644 --- a/examples/modularize.rs +++ b/examples/modularize.rs @@ -5,43 +5,75 @@ //! //! The [`calc`] and [`clock`] modules both expose a [`quic_rpc::Service`] in a regular fashion. //! They do not `use` anything from `super` or `app` so they could live in their own crates -//! unchanged. +//! unchanged. -use quic_rpc::{transport::flume, RpcServer}; +use anyhow::Result; +use futures::TryStreamExt; +use quic_rpc::{transport::flume, RpcClient, RpcServer, ServiceConnection, ServiceEndpoint}; use tracing::warn; +use app::AppService; + #[tokio::main] -async fn main() -> anyhow::Result<()> { +async fn main() -> Result<()> { // Spawn an inmemory connection. // Could use quic equally (all code in this example is generic over the transport) let (server_conn, client_conn) = flume::connection::(1); // spawn the server - tokio::task::spawn(async move { - let server = RpcServer::new(server_conn); - // create our app handler which composes the other handlers - let handler = app::Handler::default(); - loop { - match server.accept().await { - Err(err) => warn!(?err, "server accept failed"), - Ok((req, chan)) => { - let handler = handler.clone(); - tokio::task::spawn(async move { - if let Err(err) = handler.handle_rpc_request(req, chan).await { - warn!(?err, "internal rpc error"); - } - }); - } - } - } - }); + let handler = app::Handler::default(); + tokio::task::spawn(run_server(server_conn, handler)); - // run a client - app::client_demo(client_conn).await?; + // run a client demo + client_demo(client_conn).await?; Ok(()) } +async fn run_server>(server_conn: C, handler: app::Handler) { + let server = RpcServer::new(server_conn); + loop { + match server.accept().await { + Err(err) => warn!(?err, "server accept failed"), + Ok((req, chan)) => { + let handler = handler.clone(); + tokio::task::spawn(async move { + if let Err(err) = handler.handle_rpc_request(req, chan).await { + warn!(?err, "internal rpc error"); + } + }); + } + } + } +} +pub async fn client_demo>(conn: C) -> Result<()> { + let rpc_client = RpcClient::new(conn); + let client = app::Client::new(rpc_client.clone()); + + // call a method from the top-level app client + let res = client.app_version().await?; + println!("app_version: {res:?}"); + + // call a method from the wrapped iroh.calc client + let res = client.iroh.calc.add(40, 2).await?; + println!("iroh.calc.add: {res:?}"); + + // can also do "raw" calls without using the wrapped clients + let res = rpc_client + .map::() + .map::() + .rpc(calc::AddRequest(19, 4)) + .await?; + println!("iroh.calc.add (raw): {res:?}"); + + // call a server-streaming method from the wrapped iroh.clock client + let mut stream = client.iroh.clock.tick().await?; + while let Some(tick) = stream.try_next().await? { + println!("iroh.clock.tick: {tick}"); + } + Ok(()) +} + mod app { //! This is the app-specific code. //! @@ -52,7 +84,6 @@ mod app { use anyhow::Result; use derive_more::{From, TryInto}; - use futures::StreamExt; use quic_rpc::{ message::RpcMsg, server::RpcChannel, RpcClient, Service, ServiceConnection, ServiceEndpoint, }; @@ -84,7 +115,7 @@ mod app { #[derive(Copy, Clone, Debug)] pub struct AppService; - impl quic_rpc::Service for AppService { + impl Service for AppService { type Req = Request; type Res = Response; } @@ -145,24 +176,6 @@ mod app { Ok(res.0) } } - - pub async fn client_demo>(conn: C) -> Result<()> { - let client = RpcClient::::new(conn); - let client = Client::new(client); - println!("app service: version"); - let res = client.app_version().await?; - println!("app service: version res {res:?}"); - println!("calc service: add"); - let res = client.iroh.calc.add(40, 2).await?; - println!("calc service: res {res:?}"); - println!("clock service: start tick"); - let mut stream = client.iroh.clock.tick().await?; - while let Some(tick) = stream.next().await { - let tick = tick?; - println!("clock service: tick {tick}"); - } - Ok(()) - } } mod iroh { @@ -191,7 +204,7 @@ mod iroh { #[derive(Copy, Clone, Debug)] pub struct IrohService; - impl quic_rpc::Service for IrohService { + impl Service for IrohService { type Req = Request; type Res = Response; } @@ -274,7 +287,7 @@ mod calc { #[derive(Copy, Clone, Debug)] pub struct CalcService; - impl quic_rpc::Service for CalcService { + impl Service for CalcService { type Req = Request; type Res = Response; } @@ -371,7 +384,7 @@ mod clock { #[derive(Copy, Clone, Debug)] pub struct ClockService; - impl quic_rpc::Service for ClockService { + impl Service for ClockService { type Req = Request; type Res = Response; } diff --git a/src/client.rs b/src/client.rs index bb650eb..4f07c48 100644 --- a/src/client.rs +++ b/src/client.rs @@ -26,12 +26,12 @@ use std::{ /// This is a wrapper around a [ServiceConnection] that serves as the entry point /// for the client DSL. `S` is the service type, `C` is the substream source. #[derive(Debug)] -pub struct RpcClient { +pub struct RpcClient { source: C, - map: Arc>, + map: Arc>, } -impl Clone for RpcClient { +impl Clone for RpcClient { fn clone(&self) -> Self { Self { source: self.source.clone(), @@ -44,23 +44,23 @@ impl Clone for RpcClient { /// that support it, [crate::message::ClientStreaming] and [crate::message::BidiStreaming]. #[pin_project] #[derive(Debug)] -pub struct UpdateSink( +pub struct UpdateSink( #[pin] C::SendSink, PhantomData, - Arc>, + Arc>, ) where S: Service, - S2: Service, + SInner: Service, C: ServiceConnection, - T: Into; + T: Into; -impl Sink for UpdateSink +impl Sink for UpdateSink where S: Service, - S2: Service, + SInner: Service, C: ServiceConnection, - T: Into, + T: Into, { type Error = C::SendError; @@ -69,7 +69,7 @@ where } fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { - let req = self.2.req_up(item.into()); + let req = self.2.req_into_outer(item.into()); self.project().0.start_send_unpin(req) } @@ -99,11 +99,11 @@ where } } -impl RpcClient +impl RpcClient where S: Service, C: ServiceConnection, - S2: Service, + SInner: Service, { /// Get the underlying connection pub fn into_inner(self) -> C { @@ -113,17 +113,17 @@ where /// Map this channel's service into an inner service. /// /// This method is available if the required bounds are upheld: - /// S3::Req: Into + TryFrom, - /// S3::Res: Into + TryFrom, + /// SNext::Req: Into + TryFrom, + /// SNext::Res: Into + TryFrom, /// - /// Where S3 is the new service to map to and S2 is the current inner service. + /// Where SNext is the new service to map to and SInner is the current inner service. /// /// This method can be chained infintely. - pub fn map(self) -> RpcClient + pub fn map(self) -> RpcClient where - S3: Service, - S3::Req: Into + TryFrom, - S3::Res: Into + TryFrom, + SNext: Service, + SNext::Req: Into + TryFrom, + SNext::Res: Into + TryFrom, { let map = ChainedMapper::new(self.map); RpcClient { @@ -135,9 +135,9 @@ where /// RPC call to the server, single request, single response pub async fn rpc(&self, msg: M) -> result::Result> where - M: RpcMsg, + M: RpcMsg, { - let msg = self.map.req_up(msg.into()); + let msg = self.map.req_into_outer(msg.into()); let (mut send, mut recv) = self.source.open_bi().await.map_err(RpcClientError::Open)?; send.send(msg).await.map_err(RpcClientError::::Send)?; let res = recv @@ -149,7 +149,7 @@ where drop(send); let res = self .map - .try_res_down(res) + .res_try_into_inner(res) .map_err(|_| RpcClientError::DowncastError)?; M::Response::try_from(res).map_err(|_| RpcClientError::DowncastError) } @@ -163,9 +163,9 @@ where StreamingResponseError, > where - M: ServerStreamingMsg, + M: ServerStreamingMsg, { - let msg = self.map.req_up(msg.into()); + let msg = self.map.req_into_outer(msg.into()); let (mut send, recv) = self .source .open_bi() @@ -178,7 +178,7 @@ where let recv = recv.map(move |x| match x { Ok(x) => { let x = map - .try_res_down(x) + .res_try_into_inner(x) .map_err(|_| StreamingResponseItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| StreamingResponseItemError::DowncastError) } @@ -195,22 +195,22 @@ where msg: M, ) -> result::Result< ( - UpdateSink, + UpdateSink, BoxFuture<'static, result::Result>>, ), ClientStreamingError, > where - M: ClientStreamingMsg, + M: ClientStreamingMsg, { - let msg = self.map.req_up(msg.into()); + let msg = self.map.req_into_outer(msg.into()); let (mut send, mut recv) = self .source .open_bi() .await .map_err(ClientStreamingError::Open)?; send.send(msg).map_err(ClientStreamingError::Send).await?; - let send = UpdateSink::(send, PhantomData, Arc::clone(&self.map)); + let send = UpdateSink::(send, PhantomData, Arc::clone(&self.map)); let map = Arc::clone(&self.map); let recv = async move { let item = recv @@ -221,7 +221,7 @@ where match item { Ok(x) => { let x = map - .try_res_down(x) + .res_try_into_inner(x) .map_err(|_| ClientStreamingItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| ClientStreamingItemError::DowncastError) } @@ -238,15 +238,15 @@ where msg: M, ) -> result::Result< ( - UpdateSink, + UpdateSink, BoxStream<'static, result::Result>>, ), BidiError, > where - M: BidiStreamingMsg, + M: BidiStreamingMsg, { - let msg = self.map.req_up(msg.into()); + let msg = self.map.req_into_outer(msg.into()); let (mut send, recv) = self.source.open_bi().await.map_err(BidiError::Open)?; send.send(msg).await.map_err(BidiError::::Send)?; let send = UpdateSink(send, PhantomData, Arc::clone(&self.map)); @@ -255,7 +255,7 @@ where .map(move |x| match x { Ok(x) => { let x = map - .try_res_down(x) + .res_try_into_inner(x) .map_err(|_| BidiItemError::DowncastError)?; M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError) } @@ -266,7 +266,12 @@ where } } -impl, S2: Service> AsRef for RpcClient { +impl AsRef for RpcClient +where + S: Service, + C: ServiceConnection, + SInner: Service, +{ fn as_ref(&self) -> &C { &self.source } diff --git a/src/map.rs b/src/map.rs index db6e716..3915b64 100644 --- a/src/map.rs +++ b/src/map.rs @@ -2,113 +2,148 @@ use std::{marker::PhantomData, sync::Arc}; use crate::Service; -pub trait MapService: std::fmt::Debug + Send + Sync + 'static { - fn req_up(&self, req: S2::Req) -> S1::Req; - fn res_up(&self, res: S2::Res) -> S1::Res; - fn try_req_down(&self, req: S1::Req) -> Result; - fn try_res_down(&self, res: S1::Res) -> Result; +/// Convert requests and responses between an outer and an inner service. +/// +/// An "outer" service has request and response enums which wrap the requests and responses of an +/// "inner" service. This trait is implemented on the [`Mapper`] and [`ChainedMapper`] structs +/// to convert the requests and responses between the outer and inner services. +pub trait MapService: + std::fmt::Debug + Send + Sync + 'static +{ + /// Convert an inner request into the outer request. + fn req_into_outer(&self, req: SInner::Req) -> SOuter::Req; + + /// Convert an inner response into the outer response. + fn res_into_outer(&self, res: SInner::Res) -> SOuter::Res; + + /// Try to convert the outer request into the inner request. + /// + /// Returns an error if the request is not of the variant of the inner service. + fn req_try_into_inner(&self, req: SOuter::Req) -> Result; + + /// Try to convert the outer response into the inner request. + /// + /// Returns an error if the response is not of the variant of the inner service. + fn res_try_into_inner(&self, res: SOuter::Res) -> Result; } +/// Zero-sized struct to map between two services. #[derive(Debug)] -pub struct Mapper { - s1: PhantomData, - s2: PhantomData, -} +pub struct Mapper(PhantomData, PhantomData); -impl Mapper +impl Mapper where - S1: Service, - S2: Service, - S2::Req: Into + TryFrom, - S2::Res: Into + TryFrom, + SOuter: Service, + SInner: Service, + SInner::Req: Into + TryFrom, + SInner::Res: Into + TryFrom, { + /// Create a new mapper between `SOuter` and `SInner` services. + /// + /// This method is availalbe if the required bounds to convert between the outer and inner + /// request and response enums are met: + /// `SInner::Req: Into + TryFrom` + /// `SInner::Res: Into + TryFrom` pub fn new() -> Self { - Self { - s1: PhantomData, - s2: PhantomData, - } + Self(PhantomData, PhantomData) } } -impl MapService for Mapper +impl MapService for Mapper where - S1: Service, - S2: Service, - S2::Req: Into + TryFrom, - S2::Res: Into + TryFrom, + SOuter: Service, + SInner: Service, + SInner::Req: Into + TryFrom, + SInner::Res: Into + TryFrom, { - fn req_up(&self, req: S2::Req) -> S1::Req { - (req.into()).into() + fn req_into_outer(&self, req: SInner::Req) -> SOuter::Req { + req.into() } - fn res_up(&self, res: S2::Res) -> S1::Res { - (res.into()).into() + fn res_into_outer(&self, res: SInner::Res) -> SOuter::Res { + res.into() } - fn try_req_down(&self, req: S1::Req) -> Result { + fn req_try_into_inner(&self, req: SOuter::Req) -> Result { req.try_into().map_err(|_| ()) } - fn try_res_down(&self, res: S1::Res) -> Result { + fn res_try_into_inner(&self, res: SOuter::Res) -> Result { res.try_into().map_err(|_| ()) } } +/// Map between an outer and an inner service with any number of intermediate services. +/// +/// This uses an `Arc` to contain an unlimited chain of [`Mapper`]s. #[derive(Debug)] -pub struct ChainedMapper +pub struct ChainedMapper where - S1: Service, - S2: Service, - S3: Service, - S3::Req: Into + TryFrom, + SOuter: Service, + SMid: Service, + SInner: Service, + SInner::Req: Into + TryFrom, { - m1: Arc>, - m2: Mapper, + map1: Arc>, + map2: Mapper, } -impl ChainedMapper +impl ChainedMapper where - S1: Service, - S2: Service, - S3: Service, - S3::Req: Into + TryFrom, - S3::Res: Into + TryFrom, + SOuter: Service, + SMid: Service, + SInner: Service, + SInner::Req: Into + TryFrom, + SInner::Res: Into + TryFrom, { - pub fn new(m1: Arc>) -> Self { + /// Create a new [`ChainedMapper`] by appending a service `SInner` to the existing `dyn + /// MapService`. + /// + /// Usage example: + /// ```ignore + /// // S1 is a Service and impls the Into and TryFrom traits to map to S2 + /// // S2 is a Service and impls the Into and TryFrom traits to map to S3 + /// // S3 is also a Service + /// + /// let mapper: Mapper = Mapper::new(); + /// let mapper: Arc> = Arc::new(mapper); + /// let chained_mapper: ChainedMapper = ChainedMapper::new(mapper); + /// ``` + pub fn new(map1: Arc>) -> Self { Self { - m1, - m2: Mapper::new(), + map1, + map2: Mapper::new(), } } } -impl MapService for ChainedMapper +impl MapService for ChainedMapper where - S1: Service, - S2: Service, - S3: Service, - S3::Req: Into + TryFrom, - S3::Res: Into + TryFrom, + SOuter: Service, + SMid: Service, + SInner: Service, + SInner::Req: Into + TryFrom, + SInner::Res: Into + TryFrom, { - fn req_up(&self, req: S3::Req) -> S1::Req { - let req = self.m2.req_up(req); - let req = self.m1.req_up(req.into()); + fn req_into_outer(&self, req: SInner::Req) -> SOuter::Req { + let req = self.map2.req_into_outer(req); + let req = self.map1.req_into_outer(req); req } - fn res_up(&self, res: S3::Res) -> S1::Res { - let res = self.m2.res_up(res); - let res = self.m1.res_up(res.into()); + fn res_into_outer(&self, res: SInner::Res) -> SOuter::Res { + let res = self.map2.res_into_outer(res); + let res = self.map1.res_into_outer(res); res } - fn try_req_down(&self, req: S1::Req) -> Result { - let req = self.m1.try_req_down(req)?; - let req = self.m2.try_req_down(req.try_into().map_err(|_| ())?); + fn req_try_into_inner(&self, req: SOuter::Req) -> Result { + let req = self.map1.req_try_into_inner(req)?; + let req = self.map2.req_try_into_inner(req); req } - fn try_res_down(&self, res: ::Res) -> Result<::Res, ()> { - let res = self.m1.try_res_down(res)?; - let res = self.m2.try_res_down(res.try_into().map_err(|_| ())?); + fn res_try_into_inner(&self, res: SOuter::Res) -> Result { + let res = self.map1.res_try_into_inner(res)?; + let res = self.map2.res_try_into_inner(res); res } } diff --git a/src/server.rs b/src/server.rs index 2fec156..8b7ee47 100644 --- a/src/server.rs +++ b/src/server.rs @@ -62,13 +62,13 @@ impl> RpcServer { /// Sink and stream are independent, so you can take the channel apart and use /// them independently. #[derive(Debug)] -pub struct RpcChannel, S2: Service = S> { +pub struct RpcChannel, SInner: Service = S> { /// Sink to send responses to the client. pub send: C::SendSink, /// Stream to receive requests from the client. pub recv: C::RecvStream, /// Mapper to map between S and S2 - map: Arc>, + map: Arc>, } impl RpcChannel @@ -86,26 +86,26 @@ where } } -impl RpcChannel +impl RpcChannel where S: Service, C: ServiceEndpoint, - S2: Service, + SInner: Service, { /// Map this channel's service into an inner service. /// /// This method is available if the required bounds are upheld: - /// S3::Req: Into + TryFrom, - /// S3::Res: Into + TryFrom, + /// SNext::Req: Into + TryFrom, + /// SNext::Res: Into + TryFrom, /// - /// Where S3 is the new service to map to and S2 is the current inner service. + /// Where SNext is the new service to map to and SInner is the current inner service. /// /// This method can be chained infintely. - pub fn map(self) -> RpcChannel + pub fn map(self) -> RpcChannel where - S3: Service, - S3::Req: Into + TryFrom, - S3::Res: Into + TryFrom, + SNext: Service, + SNext::Req: Into + TryFrom, + SNext::Res: Into + TryFrom, { let map = ChainedMapper::new(self.map); RpcChannel { @@ -125,7 +125,7 @@ where f: F, ) -> result::Result<(), RpcServerError> where - M: RpcMsg, + M: RpcMsg, F: FnOnce(T, M) -> Fut, Fut: Future, T: Send + 'static, @@ -142,7 +142,7 @@ where // get the response let res = f(target, req).await; // turn into a S::Res so we can send it - let res = self.map.res_up(res.into()); + let res = self.map.res_into_outer(res.into()); // send it and return the error if any send.send(res).await.map_err(RpcServerError::SendError) }) @@ -159,7 +159,7 @@ where f: F, ) -> result::Result<(), RpcServerError> where - M: ClientStreamingMsg, + M: ClientStreamingMsg, F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, Fut: Future + Send + 'static, T: Send + 'static, @@ -170,7 +170,7 @@ where // get the response let res = f(target, req, updates).await; // turn into a S::Res so we can send it - let res = self.map.res_up(res.into()); + let res = self.map.res_into_outer(res.into()); // send it and return the error if any send.send(res).await.map_err(RpcServerError::SendError) }) @@ -187,7 +187,7 @@ where f: F, ) -> result::Result<(), RpcServerError> where - M: BidiStreamingMsg, + M: BidiStreamingMsg, F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, Str: Stream + Send + 'static, T: Send + 'static, @@ -201,7 +201,7 @@ where tokio::pin!(responses); while let Some(response) = responses.next().await { // turn into a S::Res so we can send it - let response = self.map.res_up(response.into()); + let response = self.map.res_into_outer(response.into()); // send it and return the error if any send.send(response) .await @@ -222,7 +222,7 @@ where f: F, ) -> result::Result<(), RpcServerError> where - M: ServerStreamingMsg, + M: ServerStreamingMsg, F: FnOnce(T, M) -> Str + Send + 'static, Str: Stream + Send + 'static, T: Send + 'static, @@ -241,7 +241,7 @@ where tokio::pin!(responses); while let Some(response) = responses.next().await { // turn into a S::Res so we can send it - let response = self.map.res_up(response.into()); + let response = self.map.res_into_outer(response.into()); // send it and return the error if any send.send(response) .await @@ -263,7 +263,7 @@ where f: F, ) -> result::Result<(), RpcServerError> where - M: RpcMsg>, + M: RpcMsg>, F: FnOnce(T, M) -> Fut, Fut: Future>, E2: From, From 2bb27d0b9ae912203f3292c527863e8203bbc619 Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Mon, 11 Mar 2024 16:49:52 +0100 Subject: [PATCH 24/49] fix: try to make client streaming and bidi streaming work --- examples/modularize.rs | 58 ++++++++++++++++++++++++++++++++++++++--- src/client.rs | 19 +++++++------- src/map.rs | 2 +- src/server.rs | 59 ++++++++++++++++++++++++++++-------------- 4 files changed, 105 insertions(+), 33 deletions(-) diff --git a/examples/modularize.rs b/examples/modularize.rs index c2956f3..ce3a72e 100644 --- a/examples/modularize.rs +++ b/examples/modularize.rs @@ -8,7 +8,7 @@ //! unchanged. use anyhow::Result; -use futures::TryStreamExt; +use futures::{SinkExt, TryStreamExt}; use quic_rpc::{transport::flume, RpcClient, RpcServer, ServiceConnection, ServiceEndpoint}; use tracing::warn; @@ -60,12 +60,25 @@ pub async fn client_demo>(conn: C) -> Result<() // can also do "raw" calls without using the wrapped clients let res = rpc_client + .clone() .map::() .map::() .rpc(calc::AddRequest(19, 4)) .await?; println!("iroh.calc.add (raw): {res:?}"); + let (mut sink, res) = rpc_client + .map::() + .map::() + .client_streaming(calc::SumRequest) + .await?; + sink.send(calc::SumUpdate(4)).await.unwrap(); + sink.send(calc::SumUpdate(8)).await.unwrap(); + sink.send(calc::SumUpdate(30)).await.unwrap(); + drop(sink); + let res = res.await?; + println!("iroh.calc.sum (raw): {res:?}"); + // call a server-streaming method from the wrapped iroh.clock client let mut stream = client.iroh.clock.tick().await?; while let Some(tick) = stream.try_next().await? { @@ -257,10 +270,13 @@ mod calc { //! This is a library providing a service, and a client. E.g. iroh-bytes or iroh-hypermerge. //! It does not use any `super` imports, it is completely decoupled. - use anyhow::Result; + use anyhow::{bail, Result}; use derive_more::{From, TryInto}; + use futures::{Stream, StreamExt}; use quic_rpc::{ - message::RpcMsg, server::RpcChannel, RpcClient, Service, ServiceConnection, ServiceEndpoint, + message::{ClientStreaming, ClientStreamingMsg, Msg, RpcMsg}, + server::RpcChannel, + RpcClient, Service, ServiceConnection, ServiceEndpoint, }; use serde::{Deserialize, Serialize}; use std::fmt::Debug; @@ -275,14 +291,35 @@ mod calc { #[derive(Debug, Serialize, Deserialize)] pub struct AddResponse(pub i64); + #[derive(Debug, Serialize, Deserialize)] + pub struct SumRequest; + + #[derive(Debug, Serialize, Deserialize)] + pub struct SumUpdate(pub i64); + + impl Msg for SumRequest { + type Pattern = ClientStreaming; + } + + impl ClientStreamingMsg for SumRequest { + type Update = SumUpdate; + type Response = SumResponse; + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct SumResponse(pub i64); + #[derive(Debug, Serialize, Deserialize, From, TryInto)] pub enum Request { Add(AddRequest), + Sum(SumRequest), + SumUpdate(SumUpdate), } #[derive(Debug, Serialize, Deserialize, From, TryInto)] pub enum Response { Add(AddResponse), + Sum(SumResponse), } #[derive(Copy, Clone, Debug)] @@ -307,6 +344,8 @@ mod calc { { match req { Request::Add(req) => chan.rpc(req, self, Self::on_add).await?, + Request::Sum(req) => chan.client_streaming(req, self, Self::on_sum).await?, + Request::SumUpdate(_) => bail!("Unexpected update message at start of request"), } Ok(()) } @@ -314,6 +353,19 @@ mod calc { pub async fn on_add(self, req: AddRequest) -> AddResponse { AddResponse(req.0 + req.1) } + + pub async fn on_sum( + self, + _req: SumRequest, + updates: impl Stream, + ) -> SumResponse { + let mut sum = 0i64; + tokio::pin!(updates); + while let Some(SumUpdate(n)) = updates.next().await { + sum += n; + } + SumResponse(sum) + } } #[derive(Debug, Clone)] diff --git a/src/client.rs b/src/client.rs index b1018ab..1c412c7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -252,16 +252,15 @@ where send.send(msg).await.map_err(BidiError::::Send)?; let send = UpdateSink(send, PhantomData, Arc::clone(&self.map)); let map = Arc::clone(&self.map); - let recv = Box::pin(recv - .map(move |x| match x { - Ok(x) => { - let x = map - .res_try_into_inner(x) - .map_err(|_| BidiItemError::DowncastError)?; - M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError) - } - Err(e) => Err(BidiItemError::RecvError(e)), - })); + let recv = Box::pin(recv.map(move |x| match x { + Ok(x) => { + let x = map + .res_try_into_inner(x) + .map_err(|_| BidiItemError::DowncastError)?; + M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError) + } + Err(e) => Err(BidiItemError::RecvError(e)), + })); Ok((send, recv)) } } diff --git a/src/map.rs b/src/map.rs index 3915b64..5276dad 100644 --- a/src/map.rs +++ b/src/map.rs @@ -13,7 +13,7 @@ pub trait MapService: /// Convert an inner request into the outer request. fn req_into_outer(&self, req: SInner::Req) -> SOuter::Req; - /// Convert an inner response into the outer response. + /// Convert an inner response into the outer response. fn res_into_outer(&self, res: SInner::Res) -> SOuter::Res; /// Try to convert the outer request into the inner request. diff --git a/src/server.rs b/src/server.rs index 8b7ee47..21b01e7 100644 --- a/src/server.rs +++ b/src/server.rs @@ -160,12 +160,12 @@ where ) -> result::Result<(), RpcServerError> where M: ClientStreamingMsg, - F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, + F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, Fut: Future + Send + 'static, T: Send + 'static, { let Self { mut send, recv, .. } = self; - let (updates, read_error) = UpdateStream::new(recv); + let (updates, read_error) = UpdateStream::new(recv, Arc::clone(&self.map)); race2(read_error.map(Err), async move { // get the response let res = f(target, req, updates).await; @@ -188,13 +188,13 @@ where ) -> result::Result<(), RpcServerError> where M: BidiStreamingMsg, - F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, + F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, Str: Stream + Send + 'static, T: Send + 'static, { let Self { mut send, recv, .. } = self; // downcast the updates - let (updates, read_error) = UpdateStream::new(recv); + let (updates, read_error) = UpdateStream::new(recv, Arc::clone(&self.map)); // get the response let responses = f(target, req, updates); race2(read_error.map(Err), async move { @@ -326,23 +326,40 @@ impl> AsRef for RpcServer { /// cause a termination of the RPC call. #[pin_project] #[derive(Debug)] -pub struct UpdateStream, T>( +pub struct UpdateStream( #[pin] C::RecvStream, Option>>, PhantomData, -); + Arc>, +) +where + S: Service, + SInner: Service, + C: ServiceEndpoint; -impl, T> UpdateStream { - fn new(recv: C::RecvStream) -> (Self, UnwrapToPending>) { +impl UpdateStream +where + S: Service, + SInner: Service, + C: ServiceEndpoint, + T: TryFrom, +{ + fn new( + recv: C::RecvStream, + map: Arc>, + ) -> (Self, UnwrapToPending>) { let (error_send, error_recv) = oneshot::channel(); let error_recv = UnwrapToPending(error_recv); - (Self(recv, Some(error_send), PhantomData), error_recv) + (Self(recv, Some(error_send), PhantomData, map), error_recv) } } -impl, T> Stream for UpdateStream +impl Stream for UpdateStream where - T: TryFrom, + S: Service, + SInner: Service, + C: ServiceEndpoint, + T: TryFrom, { type Item = T; @@ -350,16 +367,20 @@ where let mut this = self.project(); match this.0.poll_next_unpin(cx) { Poll::Ready(Some(msg)) => match msg { - Ok(msg) => match T::try_from(msg) { - Ok(msg) => Poll::Ready(Some(msg)), - Err(_cause) => { - // we were unable to downcast, so we need to send an error - if let Some(tx) = this.1.take() { - let _ = tx.send(RpcServerError::UnexpectedUpdateMessage); + Ok(msg) => { + let msg = this.3.req_try_into_inner(msg); + let msg = msg.and_then(|msg| T::try_from(msg).map_err(|_cause| ())); + match msg { + Ok(msg) => Poll::Ready(Some(msg)), + Err(_cause) => { + // we were unable to downcast, so we need to send an error + if let Some(tx) = this.1.take() { + let _ = tx.send(RpcServerError::UnexpectedUpdateMessage); + } + Poll::Pending } - Poll::Pending } - }, + } Err(cause) => { // we got a recv error, so return pending and send the error if let Some(tx) = this.1.take() { From 689962b3c6268725ac3bae71e899f9f896b0e61a Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Mon, 11 Mar 2024 17:00:04 +0100 Subject: [PATCH 25/49] add modularize tests --- tests/flume.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++- tests/math.rs | 45 ++++++++++++++++++----------- 2 files changed, 104 insertions(+), 18 deletions(-) diff --git a/tests/flume.rs b/tests/flume.rs index 13a633b..e2674e7 100644 --- a/tests/flume.rs +++ b/tests/flume.rs @@ -1,7 +1,11 @@ #![cfg(feature = "flume-transport")] mod math; use math::*; -use quic_rpc::{server::RpcServerError, transport::flume, RpcClient, RpcServer}; +use quic_rpc::{ + server::{RpcChannel, RpcServerError}, + transport::flume, + RpcClient, RpcServer, Service, +}; #[tokio::test] async fn flume_channel_bench() -> anyhow::Result<()> { @@ -20,6 +24,77 @@ async fn flume_channel_bench() -> anyhow::Result<()> { Ok(()) } +#[tokio::test] +async fn flume_channel_mapped_bench() -> anyhow::Result<()> { + use derive_more::{From, TryInto}; + use serde::{Deserialize, Serialize}; + + tracing_subscriber::fmt::try_init().ok(); + + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + enum OuterRequest { + Inner(InnerRequest), + } + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + enum InnerRequest { + Compute(ComputeRequest), + } + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + enum OuterResponse { + Inner(InnerResponse), + } + #[derive(Debug, Serialize, Deserialize, From, TryInto)] + enum InnerResponse { + Compute(ComputeResponse), + } + #[derive(Debug, Clone)] + struct OuterService; + impl Service for OuterService { + type Req = OuterRequest; + type Res = OuterResponse; + } + #[derive(Debug, Clone)] + struct InnerService; + impl Service for InnerService { + type Req = InnerRequest; + type Res = InnerResponse; + } + let (server, client) = flume::connection::(1); + + let server = RpcServer::::new(server); + // let server: RpcServer = server.map(); + // let server: RpcServer = server.map(); + let server_handle: tokio::task::JoinHandle>> = + tokio::task::spawn(async move { + let service = ComputeService; + loop { + let (req, chan) = server.accept().await?; + let service = service.clone(); + tokio::spawn(async move { + let req: OuterRequest = req; + match req { + OuterRequest::Inner(InnerRequest::Compute(req)) => { + let chan: RpcChannel = chan.map(); + let chan: RpcChannel = chan.map(); + ComputeService::handle_rpc_request(service, req, chan).await + } + } + }); + } + }); + + let client = RpcClient::::new(client); + let client: RpcClient = client.map(); + let client: RpcClient = client.map(); + bench(client, 1000000).await?; + // dropping the client will cause the server to terminate + match server_handle.await? { + Err(RpcServerError::Accept(_)) => {} + e => panic!("unexpected termination result {e:?}"), + } + Ok(()) +} + /// simple happy path test for all 4 patterns #[tokio::test] async fn flume_channel_smoke() -> anyhow::Result<()> { diff --git a/tests/math.rs b/tests/math.rs index 583b7ca..041b2b3 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -9,7 +9,8 @@ use derive_more::{From, TryInto}; use futures::{SinkExt, Stream, StreamExt, TryStreamExt}; use quic_rpc::{ declare_bidi_streaming, declare_client_streaming, declare_rpc, declare_server_streaming, - server::RpcServerError, RpcClient, RpcServer, Service, ServiceConnection, ServiceEndpoint, + server::{RpcChannel, RpcServerError}, + RpcClient, RpcServer, Service, ServiceConnection, ServiceEndpoint, }; use serde::{Deserialize, Serialize}; use std::{ @@ -137,22 +138,32 @@ impl ComputeService { loop { let (req, chan) = s.accept().await?; let service = service.clone(); - tokio::spawn(async move { - use ComputeRequest::*; - #[rustfmt::skip] - match req { - Sqr(msg) => chan.rpc(msg, service, ComputeService::sqr).await, - Sum(msg) => chan.client_streaming(msg, service, ComputeService::sum).await, - Fibonacci(msg) => chan.server_streaming(msg, service, ComputeService::fibonacci).await, - Multiply(msg) => chan.bidi_streaming(msg, service, ComputeService::multiply).await, - SumUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, - MultiplyUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, - }?; - Ok::<_, RpcServerError>(()) - }); + tokio::spawn(async move { Self::handle_rpc_request(service, req, chan).await }); } } + pub async fn handle_rpc_request( + service: ComputeService, + req: ComputeRequest, + chan: RpcChannel, + ) -> Result<(), RpcServerError> + where + S: Service, + E: ServiceEndpoint, + { + use ComputeRequest::*; + #[rustfmt::skip] + match req { + Sqr(msg) => chan.rpc(msg, service, ComputeService::sqr).await, + Sum(msg) => chan.client_streaming(msg, service, ComputeService::sum).await, + Fibonacci(msg) => chan.server_streaming(msg, service, ComputeService::fibonacci).await, + Multiply(msg) => chan.bidi_streaming(msg, service, ComputeService::multiply).await, + MultiplyUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, + SumUpdate(_) => Err(RpcServerError::UnexpectedStartMessage)?, + }?; + Ok(()) + } + /// Runs the service until `count` requests have been received. pub async fn server_bounded>( server: RpcServer, @@ -275,12 +286,12 @@ fn clear_line() { print!("\r{}\r", " ".repeat(80)); } -pub async fn bench>( - client: RpcClient, +pub async fn bench( + client: RpcClient, n: u64, ) -> anyhow::Result<()> where - C::SendError: std::error::Error, + C::SendError: std::error::Error, S: Service, C: ServiceConnection { // individual RPCs { From 63bc8d882453f45ee51187bca9e1399a928d417a Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Mon, 11 Mar 2024 17:46:14 +0100 Subject: [PATCH 26/49] chore: fmt --- tests/math.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/math.rs b/tests/math.rs index 041b2b3..699f63f 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -286,12 +286,11 @@ fn clear_line() { print!("\r{}\r", " ".repeat(80)); } -pub async fn bench( - client: RpcClient, - n: u64, -) -> anyhow::Result<()> +pub async fn bench(client: RpcClient, n: u64) -> anyhow::Result<()> where - C::SendError: std::error::Error, S: Service, C: ServiceConnection + C::SendError: std::error::Error, + S: Service, + C: ServiceConnection, { // individual RPCs { From 316e1ea328c036a1662dde98eca5feed49da859c Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 12 Mar 2024 11:22:09 +0200 Subject: [PATCH 27/49] clippy --- src/map.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/map.rs b/src/map.rs index 5276dad..597ca93 100644 --- a/src/map.rs +++ b/src/map.rs @@ -127,23 +127,19 @@ where { fn req_into_outer(&self, req: SInner::Req) -> SOuter::Req { let req = self.map2.req_into_outer(req); - let req = self.map1.req_into_outer(req); - req + self.map1.req_into_outer(req) } fn res_into_outer(&self, res: SInner::Res) -> SOuter::Res { let res = self.map2.res_into_outer(res); - let res = self.map1.res_into_outer(res); - res + self.map1.res_into_outer(res) } fn req_try_into_inner(&self, req: SOuter::Req) -> Result { let req = self.map1.req_try_into_inner(req)?; - let req = self.map2.req_try_into_inner(req); - req + self.map2.req_try_into_inner(req) } fn res_try_into_inner(&self, res: SOuter::Res) -> Result { let res = self.map1.res_try_into_inner(res)?; - let res = self.map2.res_try_into_inner(res); - res + self.map2.res_try_into_inner(res) } } From 1652d5fb39e464ad8929861614ad1a3153d3feea Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 12 Mar 2024 11:36:32 +0200 Subject: [PATCH 28/49] chore: clippy --- tests/hyper.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/hyper.rs b/tests/hyper.rs index 6b26cd5..90c6e4f 100644 --- a/tests/hyper.rs +++ b/tests/hyper.rs @@ -1,5 +1,5 @@ #![cfg(feature = "hyper-transport")] -use std::{net::SocketAddr, result}; +use std::{assert, net::SocketAddr, result}; use ::hyper::Uri; use derive_more::{From, TryInto}; @@ -283,7 +283,7 @@ async fn hyper_channel_errors() -> anyhow::Result<()> { // response small - should succeed let res = client.rpc(BigResponseRequest(10_000_000)).await; - assert_matches!(res, Ok(_)); + assert!(res.is_ok()); assert_server_result!(Ok(())); // response big - should fail From 9c4a7e69b186c84d16464de55b4d80baab73c41b Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 12 Mar 2024 11:46:19 +0200 Subject: [PATCH 29/49] chore: fix feature flags for tests this enables flume by default, and adds macros for the various tests because the protocol definition for the test protocol requires macros. --- Cargo.toml | 4 ++-- tests/flume.rs | 2 +- tests/hyper.rs | 4 ++-- tests/math.rs | 11 +++++++---- tests/quinn.rs | 2 +- tests/slow_math.rs | 11 +++++++---- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bf08a19..b4b9ddd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ quinn-transport = ["flume", "quinn", "bincode", "tokio-serde", "tokio-util"] flume-transport = ["flume"] combined-transport = [] macros = [] -default = [] +default = ["flume-transport"] [[example]] name = "errors" @@ -66,7 +66,7 @@ required-features = ["flume-transport", "macros"] [[example]] name = "store" -required-features = ["flume-transport"] +required-features = ["flume-transport", "macros"] [[example]] name = "modularize" diff --git a/tests/flume.rs b/tests/flume.rs index e2674e7..b543c4d 100644 --- a/tests/flume.rs +++ b/tests/flume.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "flume-transport")] +#![cfg(all(feature = "flume-transport", feature = "macros"))] mod math; use math::*; use quic_rpc::{ diff --git a/tests/hyper.rs b/tests/hyper.rs index ba843c1..ab80d10 100644 --- a/tests/hyper.rs +++ b/tests/hyper.rs @@ -1,5 +1,5 @@ -#![cfg(feature = "hyper-transport")] -use std::{net::SocketAddr, result}; +#![cfg(all(feature = "hyper-transport", feature = "macros"))] +use std::{assert, net::SocketAddr, result}; use ::hyper::Uri; use derive_more::{From, TryInto}; diff --git a/tests/math.rs b/tests/math.rs index 699f63f..2c6a91d 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -1,7 +1,10 @@ -#![cfg(any( - feature = "flume-transport", - feature = "hyper-transport", - feature = "quinn-transport" +#![cfg(all( + any( + feature = "flume-transport", + feature = "hyper-transport", + feature = "quinn-transport" + ), + feature = "macros" ))] #![allow(dead_code)] use async_stream::stream; diff --git a/tests/quinn.rs b/tests/quinn.rs index 67b0f72..ce585e8 100644 --- a/tests/quinn.rs +++ b/tests/quinn.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "quinn-transport")] +#![cfg(all(feature = "quinn-transport", feature = "macros"))] use std::{ net::{Ipv4Addr, SocketAddr, SocketAddrV4}, sync::Arc, diff --git a/tests/slow_math.rs b/tests/slow_math.rs index fca09c5..d1fa21e 100644 --- a/tests/slow_math.rs +++ b/tests/slow_math.rs @@ -1,7 +1,10 @@ -#![cfg(any( - feature = "flume-transport", - feature = "hyper-transport", - feature = "quinn-transport" +#![cfg(all( + any( + feature = "flume-transport", + feature = "hyper-transport", + feature = "quinn-transport" + ), + feature = "macros" ))] mod math; use std::result; From ea61916b3156746d61544cc4d5311cfe1d65a86c Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 12 Mar 2024 12:07:15 +0200 Subject: [PATCH 30/49] expllcitly define the rpc types in the math and slow_math test support this is beneficial for people that want to know how it works under the hood, and also means that the tests don't need to rely on the macros feature. --- tests/flume.rs | 2 +- tests/hyper.rs | 2 +- tests/math.rs | 49 ++++++++++++++++++++++++++++++++----------- tests/quinn.rs | 2 +- tests/slow_math.rs | 52 ++++++++++++++++++++++++++++++++++------------ 5 files changed, 79 insertions(+), 28 deletions(-) diff --git a/tests/flume.rs b/tests/flume.rs index b543c4d..e2674e7 100644 --- a/tests/flume.rs +++ b/tests/flume.rs @@ -1,4 +1,4 @@ -#![cfg(all(feature = "flume-transport", feature = "macros"))] +#![cfg(feature = "flume-transport")] mod math; use math::*; use quic_rpc::{ diff --git a/tests/hyper.rs b/tests/hyper.rs index ab80d10..7f6c3a5 100644 --- a/tests/hyper.rs +++ b/tests/hyper.rs @@ -1,4 +1,4 @@ -#![cfg(all(feature = "hyper-transport", feature = "macros"))] +#![cfg(feature = "hyper-transport")] use std::{assert, net::SocketAddr, result}; use ::hyper::Uri; diff --git a/tests/math.rs b/tests/math.rs index 2c6a91d..5cc6929 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -1,17 +1,17 @@ -#![cfg(all( - any( - feature = "flume-transport", - feature = "hyper-transport", - feature = "quinn-transport" - ), - feature = "macros" +#![cfg(any( + feature = "flume-transport", + feature = "hyper-transport", + feature = "quinn-transport" ))] #![allow(dead_code)] use async_stream::stream; use derive_more::{From, TryInto}; use futures::{SinkExt, Stream, StreamExt, TryStreamExt}; use quic_rpc::{ - declare_bidi_streaming, declare_client_streaming, declare_rpc, declare_server_streaming, + message::{ + BidiStreaming, BidiStreamingMsg, ClientStreaming, ClientStreamingMsg, Msg, RpcMsg, + ServerStreaming, ServerStreamingMsg, + }, server::{RpcChannel, RpcServerError}, RpcClient, RpcServer, Service, ServiceConnection, ServiceEndpoint, }; @@ -85,10 +85,35 @@ impl Service for ComputeService { type Res = ComputeResponse; } -declare_rpc!(ComputeService, Sqr, SqrResponse); -declare_client_streaming!(ComputeService, Sum, SumUpdate, SumResponse); -declare_server_streaming!(ComputeService, Fibonacci, FibonacciResponse); -declare_bidi_streaming!(ComputeService, Multiply, MultiplyUpdate, MultiplyResponse); +impl RpcMsg for Sqr { + type Response = SqrResponse; +} + +impl Msg for Sum { + type Pattern = ClientStreaming; +} + +impl ClientStreamingMsg for Sum { + type Update = SumUpdate; + type Response = SumResponse; +} + +impl Msg for Fibonacci { + type Pattern = ServerStreaming; +} + +impl ServerStreamingMsg for Fibonacci { + type Response = FibonacciResponse; +} + +impl Msg for Multiply { + type Pattern = BidiStreaming; +} + +impl BidiStreamingMsg for Multiply { + type Update = MultiplyUpdate; + type Response = MultiplyResponse; +} impl ComputeService { async fn sqr(self, req: Sqr) -> SqrResponse { diff --git a/tests/quinn.rs b/tests/quinn.rs index ce585e8..67b0f72 100644 --- a/tests/quinn.rs +++ b/tests/quinn.rs @@ -1,4 +1,4 @@ -#![cfg(all(feature = "quinn-transport", feature = "macros"))] +#![cfg(feature = "quinn-transport")] use std::{ net::{Ipv4Addr, SocketAddr, SocketAddrV4}, sync::Arc, diff --git a/tests/slow_math.rs b/tests/slow_math.rs index d1fa21e..e68ec10 100644 --- a/tests/slow_math.rs +++ b/tests/slow_math.rs @@ -1,10 +1,7 @@ -#![cfg(all( - any( - feature = "flume-transport", - feature = "hyper-transport", - feature = "quinn-transport" - ), - feature = "macros" +#![cfg(any( + feature = "flume-transport", + feature = "hyper-transport", + feature = "quinn-transport" ))] mod math; use std::result; @@ -13,8 +10,12 @@ use async_stream::stream; use futures::{Stream, StreamExt}; use math::*; use quic_rpc::{ - declare_bidi_streaming, declare_client_streaming, declare_rpc, declare_server_streaming, - server::RpcServerError, RpcServer, Service, ServiceEndpoint, + message::{ + BidiStreaming, BidiStreamingMsg, ClientStreaming, ClientStreamingMsg, Msg, RpcMsg, + ServerStreaming, ServerStreamingMsg, + }, + server::RpcServerError, + RpcServer, Service, ServiceEndpoint, }; #[derive(Debug, Clone)] @@ -25,10 +26,35 @@ impl Service for ComputeService { type Res = ComputeResponse; } -declare_rpc!(ComputeService, Sqr, SqrResponse); -declare_client_streaming!(ComputeService, Sum, SumUpdate, SumResponse); -declare_server_streaming!(ComputeService, Fibonacci, FibonacciResponse); -declare_bidi_streaming!(ComputeService, Multiply, MultiplyUpdate, MultiplyResponse); +impl RpcMsg for Sqr { + type Response = SqrResponse; +} + +impl Msg for Sum { + type Pattern = ClientStreaming; +} + +impl ClientStreamingMsg for Sum { + type Update = SumUpdate; + type Response = SumResponse; +} + +impl Msg for Fibonacci { + type Pattern = ServerStreaming; +} + +impl ServerStreamingMsg for Fibonacci { + type Response = FibonacciResponse; +} + +impl Msg for Multiply { + type Pattern = BidiStreaming; +} + +impl BidiStreamingMsg for Multiply { + type Update = MultiplyUpdate; + type Response = MultiplyResponse; +} async fn sleep_ms(ms: u64) { tokio::time::sleep(std::time::Duration::from_millis(ms)).await; From eb0750a061f0f3494aa13c0aea643333e197ba27 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 12 Mar 2024 12:36:13 +0200 Subject: [PATCH 31/49] increase version number --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 146a4fa..1dea3fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -701,7 +701,7 @@ dependencies = [ [[package]] name = "quic-rpc" -version = "0.6.3" +version = "0.7.0" dependencies = [ "anyhow", "async-stream", diff --git a/Cargo.toml b/Cargo.toml index 8543bb9..65f41d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quic-rpc" -version = "0.6.3" +version = "0.7.0" edition = "2021" authors = ["Rüdiger Klaehn "] keywords = ["api", "protocol", "network", "rpc"] From bc2f0ac2854c45f8d6e4e3b9b7a11d3cb62775e6 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 12 Mar 2024 12:37:13 +0200 Subject: [PATCH 32/49] doc lint --- src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.rs b/src/server.rs index 21b01e7..d5be6aa 100644 --- a/src/server.rs +++ b/src/server.rs @@ -20,7 +20,7 @@ use std::{ /// A server channel for a specific service. /// -/// This is a wrapper around a [ServiceEndpoint](crate::ServiceEndpoint) that serves as the entry point for the server DSL. +/// This is a wrapper around a [ServiceEndpoint] that serves as the entry point for the server DSL. /// `S` is the service type, `C` is the channel type. #[derive(Debug)] pub struct RpcServer { From 4a04a515faa614716753f01fb7636c2e997de99c Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 26 Mar 2024 11:30:27 +0200 Subject: [PATCH 33/49] Add TryServerStreaming interaction pattern also reorganize this into a separate pattern dir. --- src/client.rs | 6 +- src/lib.rs | 2 + src/pattern/mod.rs | 2 + src/pattern/try_server_streaming.rs | 214 ++++++++++++++++++++++++++++ src/server.rs | 4 +- tests/try.rs | 103 +++++++++++++ 6 files changed, 326 insertions(+), 5 deletions(-) create mode 100644 src/pattern/mod.rs create mode 100644 src/pattern/try_server_streaming.rs create mode 100644 tests/try.rs diff --git a/src/client.rs b/src/client.rs index 1c412c7..513f0ec 100644 --- a/src/client.rs +++ b/src/client.rs @@ -28,8 +28,8 @@ pub type BoxStreamSync<'a, T> = Pin + Send + Sync + 'a> /// for the client DSL. `S` is the service type, `C` is the substream source. #[derive(Debug)] pub struct RpcClient { - source: C, - map: Arc>, + pub(crate) source: C, + pub(crate) map: Arc>, } impl Clone for RpcClient { @@ -405,7 +405,7 @@ impl error::Error for StreamingResponseItemError {} /// Wrap a stream with an additional item that is kept alive until the stream is dropped #[pin_project] -struct DeferDrop(#[pin] S, X); +pub(crate) struct DeferDrop(#[pin] pub S, pub X); impl Stream for DeferDrop { type Item = S::Item; diff --git a/src/lib.rs b/src/lib.rs index 8c6464d..bc92ea3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,6 +104,8 @@ pub use server::RpcServer; mod macros; mod map; +pub mod pattern; + /// Requirements for a RPC message /// /// Even when just using the mem transport, we require messages to be Serializable and Deserializable. diff --git a/src/pattern/mod.rs b/src/pattern/mod.rs new file mode 100644 index 0000000..5826152 --- /dev/null +++ b/src/pattern/mod.rs @@ -0,0 +1,2 @@ +//! +pub mod try_server_streaming; diff --git a/src/pattern/try_server_streaming.rs b/src/pattern/try_server_streaming.rs new file mode 100644 index 0000000..f2e9d52 --- /dev/null +++ b/src/pattern/try_server_streaming.rs @@ -0,0 +1,214 @@ +//! +use futures::{FutureExt, SinkExt, Stream, StreamExt, TryFutureExt}; +use serde::{Deserialize, Serialize}; + +use crate::{ + client::{BoxStreamSync, DeferDrop}, + message::{InteractionPattern, Msg}, + server::{race2, RpcChannel, RpcServerError}, + transport::ConnectionErrors, + RpcClient, Service, ServiceConnection, ServiceEndpoint, +}; + +use std::{ + error, + fmt::{self, Debug}, + result, + sync::Arc, +}; + +/// +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct StreamCreated; + +/// +#[derive(Debug, Clone, Copy)] +pub struct TryServerStreaming; + +impl InteractionPattern for TryServerStreaming {} + +/// Same as [ServerStreamingMsg], but with lazy stream creation and the error type explicitly defined. +pub trait TryServerStreamingMsg: Msg +where + std::result::Result: Into + TryFrom, + std::result::Result: Into + TryFrom, +{ + /// Error when creating the stream + type CreateError: Debug + Send + 'static; + + /// Error for stream items + type ItemError: Debug + Send + 'static; + + /// The type for the response + /// + /// For requests that can produce errors, this can be set to [Result](std::result::Result). + type Item: Send + 'static; +} + +/// Server error when accepting a server streaming request +/// +/// This combines network errors with application errors. Usually you don't +/// care about the exact nature of the error, but if you want to handle +/// application errors differently, you can match on this enum. +#[derive(Debug)] +pub enum Error { + /// Unable to open a substream at all + Open(C::OpenError), + /// Unable to send the request to the server + Send(C::SendError), + /// Error received when creating the stream + Recv(C::RecvError), + /// Connection was closed before receiving the first message + EarlyClose, + /// Unexpected response from the server + Downcast, + /// Application error + Application(E), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for Error {} + +/// Client error when handling responses from a server streaming request +#[derive(Debug)] +pub enum ItemError { + /// Unable to receive the response from the server + Recv(S::RecvError), + /// Unexpected response from the server + Downcast, + /// Application error + Application(E), +} + +impl fmt::Display for ItemError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for ItemError {} + +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, + SInner: Service, +{ + /// handle the message M using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + /// + /// Compared to [RpcChannel::server_streaming], with this method the stream creation is via + /// a function that returns a future that resolves to a stream. + pub async fn try_server_streaming( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: TryServerStreamingMsg, + std::result::Result: Into + TryFrom, + std::result::Result: + Into + TryFrom, + F: FnOnce(T, M) -> Fut + Send + 'static, + Fut: futures::Future> + Send + 'static, + Str: Stream> + Send + 'static, + T: Send + 'static, + { + let Self { + mut send, mut recv, .. + } = self; + // cancel if we get an update, no matter what it is + let cancel = recv + .next() + .map(|_| RpcServerError::UnexpectedUpdateMessage::); + // race the computation and the cancellation + race2(cancel.map(Err), async move { + // get the response + let responses = match f(target, req).await { + Ok(responses) => { + // turn into a S::Res so we can send it + let response = self.map.res_into_outer(Ok(StreamCreated).into()); + // send it and return the error if any + send.send(response) + .await + .map_err(RpcServerError::SendError)?; + responses + } + Err(cause) => { + // turn into a S::Res so we can send it + let response = self.map.res_into_outer(Err(cause).into()); + // send it and return the error if any + send.send(response) + .await + .map_err(RpcServerError::SendError)?; + return Ok(()); + } + }; + tokio::pin!(responses); + while let Some(response) = responses.next().await { + // turn into a S::Res so we can send it + let response = self.map.res_into_outer(response.into()); + // send it and return the error if any + send.send(response) + .await + .map_err(RpcServerError::SendError)?; + } + Ok(()) + }) + .await + } +} + +impl RpcClient +where + S: Service, + C: ServiceConnection, + SInner: Service, +{ + /// Bidi call to the server, request opens a stream, response is a stream + pub async fn try_server_streaming( + &self, + msg: M, + ) -> result::Result< + BoxStreamSync<'static, Result>>, + Error, + > + where + M: TryServerStreamingMsg, + Result: Into + TryFrom, + Result: Into + TryFrom, + { + let msg = self.map.req_into_outer(msg.into()); + let (mut send, mut recv) = self.source.open_bi().await.map_err(Error::Open)?; + send.send(msg).map_err(Error::Send).await?; + let map = Arc::clone(&self.map); + let Some(initial) = recv.next().await else { + return Err(Error::EarlyClose); + }; + let initial = initial.map_err(Error::Recv)?; // initial response + let initial = map + .res_try_into_inner(initial) + .map_err(|_| Error::Downcast)?; + let initial = >::try_from(initial) + .map_err(|_| Error::Downcast)?; + let _ = initial.map_err(Error::Application)?; + let recv = recv.map(move |x| { + let x = x.map_err(ItemError::Recv)?; + let x = map.res_try_into_inner(x).map_err(|_| ItemError::Downcast)?; + let x = >::try_from(x) + .map_err(|_| ItemError::Downcast)?; + let x = x.map_err(ItemError::Application)?; + Ok(x) + }); + // keep send alive so the request on the server side does not get cancelled + let recv = Box::pin(DeferDrop(recv, send)); + Ok(recv) + } +} diff --git a/src/server.rs b/src/server.rs index d5be6aa..0b71b4e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -68,7 +68,7 @@ pub struct RpcChannel, SInner: Service = S> { /// Stream to receive requests from the client. pub recv: C::RecvStream, /// Mapper to map between S and S2 - map: Arc>, + pub map: Arc>, } impl RpcChannel @@ -447,7 +447,7 @@ impl Future for UnwrapToPending { } } -async fn race2, B: Future>(f1: A, f2: B) -> T { +pub(crate) async fn race2, B: Future>(f1: A, f2: B) -> T { tokio::select! { x = f1 => x, x = f2 => x, diff --git a/tests/try.rs b/tests/try.rs new file mode 100644 index 0000000..be20774 --- /dev/null +++ b/tests/try.rs @@ -0,0 +1,103 @@ +#![cfg(feature = "flume-transport")] +use derive_more::{From, TryInto}; +use futures::{Stream, StreamExt}; +use quic_rpc::{ + message::Msg, + pattern::try_server_streaming::{StreamCreated, TryServerStreaming, TryServerStreamingMsg}, + server::RpcServerError, + transport::flume, + RpcClient, RpcServer, Service, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone)] +struct TryService; + +impl Service for TryService { + type Req = TryRequest; + type Res = TryResponse; +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct StreamN { + n: u64, +} + +impl Msg for StreamN { + type Pattern = TryServerStreaming; +} + +impl TryServerStreamingMsg for StreamN { + type Item = u64; + type ItemError = String; + type CreateError = String; +} + +/// request enum +#[derive(Debug, Serialize, Deserialize, From, TryInto)] +pub enum TryRequest { + StreamN(StreamN), +} + +#[derive(Debug, Serialize, Deserialize, From, TryInto, Clone)] +pub enum TryResponse { + StreamN(std::result::Result), + StreamNError(std::result::Result), +} + +#[derive(Clone)] +struct Handler; + +impl Handler { + async fn try_stream_n( + self, + req: StreamN, + ) -> std::result::Result>, String> { + if req.n % 2 != 0 { + return Err("odd n not allowed".to_string()); + } + let stream = async_stream::stream! { + for i in 0..req.n { + if i > 5 { + yield Err("n too large".to_string()); + return; + } + yield Ok(i); + } + }; + Ok(stream) + } +} + +#[tokio::test] +async fn try_server_streaming() -> anyhow::Result<()> { + tracing_subscriber::fmt::try_init().ok(); + let (server, client) = flume::connection::(1); + + let server = RpcServer::::new(server); + let server_handle = tokio::task::spawn(async move { + loop { + let (req, chan) = server.accept().await?; + let handler = Handler; + match req { + TryRequest::StreamN(req) => { + chan.try_server_streaming(req, handler, Handler::try_stream_n) + .await?; + } + } + } + #[allow(unreachable_code)] + Ok(()) + }); + let client = RpcClient::::new(client); + let stream_n = client.try_server_streaming(StreamN { n: 10 }).await?; + let items: Vec<_> = stream_n.collect().await; + println!("{:?}", items); + drop(client); + // dropping the client will cause the server to terminate + match server_handle.await? { + Err(RpcServerError::Accept(_)) => {} + e => panic!("unexpected termination result {e:?}"), + } + Ok(()) +} From 49791a872a55111b4c6c16d547292a4905344df9 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Tue, 26 Mar 2024 17:08:12 +0200 Subject: [PATCH 34/49] move the interaction patterns into separate modules --- src/client.rs | 272 +--------------------------- src/message.rs | 88 +-------- src/pattern/bidi_streaming.rs | 154 ++++++++++++++++ src/pattern/client_streaming.rs | 154 ++++++++++++++++ src/pattern/mod.rs | 4 + src/pattern/rpc.rs | 158 ++++++++++++++++ src/pattern/server_streaming.rs | 146 +++++++++++++++ src/pattern/try_server_streaming.rs | 17 +- src/server.rs | 171 +---------------- tests/hyper.rs | 19 +- 10 files changed, 651 insertions(+), 532 deletions(-) create mode 100644 src/pattern/bidi_streaming.rs create mode 100644 src/pattern/client_streaming.rs create mode 100644 src/pattern/rpc.rs create mode 100644 src/pattern/server_streaming.rs diff --git a/src/client.rs b/src/client.rs index 513f0ec..6db1341 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,18 +3,14 @@ //! The main entry point is [RpcClient]. use crate::{ map::{ChainedMapper, MapService, Mapper}, - message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, - transport::ConnectionErrors, Service, ServiceConnection, }; -use futures::{future::BoxFuture, FutureExt, Sink, SinkExt, Stream, StreamExt, TryFutureExt}; +use futures::{Sink, SinkExt, Stream}; use pin_project::pin_project; use std::{ - error, - fmt::{self, Debug}, + fmt::Debug, marker::PhantomData, pin::Pin, - result, sync::Arc, task::{Context, Poll}, }; @@ -46,9 +42,9 @@ impl Clone for RpcClient { #[pin_project] #[derive(Debug)] pub struct UpdateSink( - #[pin] C::SendSink, - PhantomData, - Arc>, + #[pin] pub C::SendSink, + pub PhantomData, + pub Arc>, ) where S: Service, @@ -132,137 +128,6 @@ where map: Arc::new(map), } } - - /// RPC call to the server, single request, single response - pub async fn rpc(&self, msg: M) -> result::Result> - where - M: RpcMsg, - { - let msg = self.map.req_into_outer(msg.into()); - let (mut send, mut recv) = self.source.open_bi().await.map_err(RpcClientError::Open)?; - send.send(msg).await.map_err(RpcClientError::::Send)?; - let res = recv - .next() - .await - .ok_or(RpcClientError::::EarlyClose)? - .map_err(RpcClientError::::RecvError)?; - // keep send alive until we have the answer - drop(send); - let res = self - .map - .res_try_into_inner(res) - .map_err(|_| RpcClientError::DowncastError)?; - M::Response::try_from(res).map_err(|_| RpcClientError::DowncastError) - } - - /// Bidi call to the server, request opens a stream, response is a stream - pub async fn server_streaming( - &self, - msg: M, - ) -> result::Result< - BoxStreamSync<'static, result::Result>>, - StreamingResponseError, - > - where - M: ServerStreamingMsg, - { - let msg = self.map.req_into_outer(msg.into()); - let (mut send, recv) = self - .source - .open_bi() - .await - .map_err(StreamingResponseError::Open)?; - send.send(msg) - .map_err(StreamingResponseError::::Send) - .await?; - let map = Arc::clone(&self.map); - let recv = recv.map(move |x| match x { - Ok(x) => { - let x = map - .res_try_into_inner(x) - .map_err(|_| StreamingResponseItemError::DowncastError)?; - M::Response::try_from(x).map_err(|_| StreamingResponseItemError::DowncastError) - } - Err(e) => Err(StreamingResponseItemError::RecvError(e)), - }); - // keep send alive so the request on the server side does not get cancelled - let recv = Box::pin(DeferDrop(recv, send)); - Ok(recv) - } - - /// Call to the server that allows the client to stream, single response - pub async fn client_streaming( - &self, - msg: M, - ) -> result::Result< - ( - UpdateSink, - BoxFuture<'static, result::Result>>, - ), - ClientStreamingError, - > - where - M: ClientStreamingMsg, - { - let msg = self.map.req_into_outer(msg.into()); - let (mut send, mut recv) = self - .source - .open_bi() - .await - .map_err(ClientStreamingError::Open)?; - send.send(msg).map_err(ClientStreamingError::Send).await?; - let send = UpdateSink::(send, PhantomData, Arc::clone(&self.map)); - let map = Arc::clone(&self.map); - let recv = async move { - let item = recv - .next() - .await - .ok_or(ClientStreamingItemError::EarlyClose)?; - - match item { - Ok(x) => { - let x = map - .res_try_into_inner(x) - .map_err(|_| ClientStreamingItemError::DowncastError)?; - M::Response::try_from(x).map_err(|_| ClientStreamingItemError::DowncastError) - } - Err(e) => Err(ClientStreamingItemError::RecvError(e)), - } - } - .boxed(); - Ok((send, recv)) - } - - /// Bidi call to the server, request opens a stream, response is a stream - pub async fn bidi( - &self, - msg: M, - ) -> result::Result< - ( - UpdateSink, - BoxStreamSync<'static, result::Result>>, - ), - BidiError, - > - where - M: BidiStreamingMsg, - { - let msg = self.map.req_into_outer(msg.into()); - let (mut send, recv) = self.source.open_bi().await.map_err(BidiError::Open)?; - send.send(msg).await.map_err(BidiError::::Send)?; - let send = UpdateSink(send, PhantomData, Arc::clone(&self.map)); - let map = Arc::clone(&self.map); - let recv = Box::pin(recv.map(move |x| match x { - Ok(x) => { - let x = map - .res_try_into_inner(x) - .map_err(|_| BidiItemError::DowncastError)?; - M::Response::try_from(x).map_err(|_| BidiItemError::DowncastError) - } - Err(e) => Err(BidiItemError::RecvError(e)), - })); - Ok((send, recv)) - } } impl AsRef for RpcClient @@ -276,133 +141,6 @@ where } } -/// Client error. All client DSL methods return a `Result` with this error type. -#[derive(Debug)] -pub enum RpcClientError { - /// Unable to open a substream at all - Open(C::OpenError), - /// Unable to send the request to the server - Send(C::SendError), - /// Server closed the stream before sending a response - EarlyClose, - /// Unable to receive the response from the server - RecvError(C::RecvError), - /// Unexpected response from the server - DowncastError, -} - -impl fmt::Display for RpcClientError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for RpcClientError {} - -/// Server error when accepting a bidi request -#[derive(Debug)] -pub enum BidiError { - /// Unable to open a substream at all - Open(C::OpenError), - /// Unable to send the request to the server - Send(C::SendError), -} - -impl fmt::Display for BidiError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for BidiError {} - -/// Server error when receiving an item for a bidi request -#[derive(Debug)] -pub enum BidiItemError { - /// Unable to receive the response from the server - RecvError(C::RecvError), - /// Unexpected response from the server - DowncastError, -} - -impl fmt::Display for BidiItemError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for BidiItemError {} - -/// Server error when accepting a client streaming request -#[derive(Debug)] -pub enum ClientStreamingError { - /// Unable to open a substream at all - Open(C::OpenError), - /// Unable to send the request to the server - Send(C::SendError), -} - -impl fmt::Display for ClientStreamingError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for ClientStreamingError {} - -/// Server error when receiving an item for a client streaming request -#[derive(Debug)] -pub enum ClientStreamingItemError { - /// Connection was closed before receiving the first message - EarlyClose, - /// Unable to receive the response from the server - RecvError(C::RecvError), - /// Unexpected response from the server - DowncastError, -} - -impl fmt::Display for ClientStreamingItemError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for ClientStreamingItemError {} - -/// Server error when accepting a server streaming request -#[derive(Debug)] -pub enum StreamingResponseError { - /// Unable to open a substream at all - Open(C::OpenError), - /// Unable to send the request to the server - Send(C::SendError), -} - -impl fmt::Display for StreamingResponseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for StreamingResponseError {} - -/// Client error when handling responses from a server streaming request -#[derive(Debug)] -pub enum StreamingResponseItemError { - /// Unable to receive the response from the server - RecvError(S::RecvError), - /// Unexpected response from the server - DowncastError, -} - -impl fmt::Display for StreamingResponseItemError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } -} - -impl error::Error for StreamingResponseItemError {} - /// Wrap a stream with an additional item that is kept alive until the stream is dropped #[pin_project] pub(crate) struct DeferDrop(#[pin] pub S, pub X); diff --git a/src/message.rs b/src/message.rs index 83b6bee..d7c9caf 100644 --- a/src/message.rs +++ b/src/message.rs @@ -4,6 +4,11 @@ use crate::Service; use std::fmt::Debug; +pub use crate::pattern::bidi_streaming::{BidiStreaming, BidiStreamingMsg}; +pub use crate::pattern::client_streaming::{ClientStreaming, ClientStreamingMsg}; +pub use crate::pattern::rpc::{Rpc, RpcMsg}; +pub use crate::pattern::server_streaming::{ServerStreaming, ServerStreamingMsg}; + /// Declares the interaction pattern for a message and a service. /// /// For each server and each message, only one interaction pattern can be defined. @@ -12,59 +17,6 @@ pub trait Msg: Into + TryFrom + Send + 'static { type Pattern: InteractionPattern; } -/// Defines the response type for a rpc message. -/// -/// Since this is the most common interaction pattern, this also implements [Msg] for you -/// automatically, with the interaction pattern set to [Rpc]. This is to reduce boilerplate -/// when defining rpc messages. -pub trait RpcMsg: Msg { - /// The type for the response - /// - /// For requests that can produce errors, this can be set to [Result](std::result::Result). - type Response: Into + TryFrom + Send + 'static; -} - -/// We can only do this for one trait, so we do it for RpcMsg since it is the most common -impl, S: Service> Msg for T { - type Pattern = Rpc; -} - -/// Defines update type and response type for a client streaming message. -pub trait ClientStreamingMsg: Msg { - /// The type for request updates - /// - /// For a request that does not support updates, this can be safely set to any type, including - /// the message type itself. Any update for such a request will result in an error. - type Update: Into + TryFrom + Send + 'static; - - /// The type for the response - /// - /// For requests that can produce errors, this can be set to [Result](std::result::Result). - type Response: Into + TryFrom + Send + 'static; -} - -/// Defines response type for a server streaming message. -pub trait ServerStreamingMsg: Msg { - /// The type for the response - /// - /// For requests that can produce errors, this can be set to [Result](std::result::Result). - type Response: Into + TryFrom + Send + 'static; -} - -/// Defines update type and response type for a bidi streaming message. -pub trait BidiStreamingMsg: Msg { - /// The type for request updates - /// - /// For a request that does not support updates, this can be safely set to any type, including - /// the message type itself. Any update for such a request will result in an error. - type Update: Into + TryFrom + Send + 'static; - - /// The type for the response - /// - /// For requests that can produce errors, this can be set to [Result](std::result::Result). - type Response: Into + TryFrom + Send + 'static; -} - /// Trait defining interaction pattern. /// /// Currently there are 4 patterns: @@ -75,33 +27,3 @@ pub trait BidiStreamingMsg: Msg { /// /// You could define your own interaction patterns such as OneWay. pub trait InteractionPattern: Debug + Clone + Send + Sync + 'static {} - -/// Rpc interaction pattern -/// -/// There is only one request and one response. -#[derive(Debug, Clone, Copy)] -pub struct Rpc; -impl InteractionPattern for Rpc {} - -/// Client streaming interaction pattern -/// -/// After the initial request, the client can send updates, but there is only -/// one response. -#[derive(Debug, Clone, Copy)] -pub struct ClientStreaming; -impl InteractionPattern for ClientStreaming {} - -/// Server streaming interaction pattern -/// -/// After the initial request, the server can send a stream of responses. -#[derive(Debug, Clone, Copy)] -pub struct ServerStreaming; -impl InteractionPattern for ServerStreaming {} - -/// Bidirectional streaming interaction pattern -/// -/// After the initial request, the client can send updates and the server can -/// send responses. -#[derive(Debug, Clone, Copy)] -pub struct BidiStreaming; -impl InteractionPattern for BidiStreaming {} diff --git a/src/pattern/bidi_streaming.rs b/src/pattern/bidi_streaming.rs new file mode 100644 index 0000000..28142a6 --- /dev/null +++ b/src/pattern/bidi_streaming.rs @@ -0,0 +1,154 @@ +//! +use futures::{FutureExt, SinkExt, Stream, StreamExt}; + +use crate::{ + client::{BoxStreamSync, UpdateSink}, + message::{InteractionPattern, Msg}, + server::{race2, RpcChannel, RpcServerError, UpdateStream}, + transport::ConnectionErrors, + RpcClient, Service, ServiceConnection, ServiceEndpoint, +}; + +use std::{ + error, + fmt::{self, Debug}, + marker::PhantomData, + result, + sync::Arc, +}; + +/// Bidirectional streaming interaction pattern +/// +/// After the initial request, the client can send updates and the server can +/// send responses. +#[derive(Debug, Clone, Copy)] +pub struct BidiStreaming; +impl InteractionPattern for BidiStreaming {} + +/// Defines update type and response type for a bidi streaming message. +pub trait BidiStreamingMsg: Msg { + /// The type for request updates + /// + /// For a request that does not support updates, this can be safely set to any type, including + /// the message type itself. Any update for such a request will result in an error. + type Update: Into + TryFrom + Send + 'static; + + /// The type for the response + /// + /// For requests that can produce errors, this can be set to [Result](std::result::Result). + type Response: Into + TryFrom + Send + 'static; +} + +/// Server error when accepting a bidi request +#[derive(Debug)] +pub enum Error { + /// Unable to open a substream at all + Open(C::OpenError), + /// Unable to send the request to the server + Send(C::SendError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for Error {} + +/// Server error when receiving an item for a bidi request +#[derive(Debug)] +pub enum ItemError { + /// Unable to receive the response from the server + RecvError(C::RecvError), + /// Unexpected response from the server + DowncastError, +} + +impl fmt::Display for ItemError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for ItemError {} + +impl RpcClient +where + S: Service, + C: ServiceConnection, + SInner: Service, +{ + /// Bidi call to the server, request opens a stream, response is a stream + pub async fn bidi( + &self, + msg: M, + ) -> result::Result< + ( + UpdateSink, + BoxStreamSync<'static, result::Result>>, + ), + Error, + > + where + M: BidiStreamingMsg, + { + let msg = self.map.req_into_outer(msg.into()); + let (mut send, recv) = self.source.open_bi().await.map_err(Error::Open)?; + send.send(msg).await.map_err(Error::::Send)?; + let send = UpdateSink(send, PhantomData, Arc::clone(&self.map)); + let map = Arc::clone(&self.map); + let recv = Box::pin(recv.map(move |x| match x { + Ok(x) => { + let x = map + .res_try_into_inner(x) + .map_err(|_| ItemError::DowncastError)?; + M::Response::try_from(x).map_err(|_| ItemError::DowncastError) + } + Err(e) => Err(ItemError::RecvError(e)), + })); + Ok((send, recv)) + } +} + +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, + SInner: Service, +{ + /// handle the message M using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + pub async fn bidi_streaming( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: BidiStreamingMsg, + F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, + Str: Stream + Send + 'static, + T: Send + 'static, + { + let Self { mut send, recv, .. } = self; + // downcast the updates + let (updates, read_error) = UpdateStream::new(recv, Arc::clone(&self.map)); + // get the response + let responses = f(target, req, updates); + race2(read_error.map(Err), async move { + tokio::pin!(responses); + while let Some(response) = responses.next().await { + // turn into a S::Res so we can send it + let response = self.map.res_into_outer(response.into()); + // send it and return the error if any + send.send(response) + .await + .map_err(RpcServerError::SendError)?; + } + Ok(()) + }) + .await + } +} diff --git a/src/pattern/client_streaming.rs b/src/pattern/client_streaming.rs new file mode 100644 index 0000000..89fc0b0 --- /dev/null +++ b/src/pattern/client_streaming.rs @@ -0,0 +1,154 @@ +//! +use futures::{future::BoxFuture, Future, FutureExt, SinkExt, StreamExt, TryFutureExt}; + +use crate::{ + client::UpdateSink, + message::{InteractionPattern, Msg}, + server::{race2, RpcChannel, RpcServerError, UpdateStream}, + transport::ConnectionErrors, + RpcClient, Service, ServiceConnection, ServiceEndpoint, +}; + +use std::{ + error, + fmt::{self, Debug}, + marker::PhantomData, + result, + sync::Arc, +}; + +/// Client streaming interaction pattern +/// +/// After the initial request, the client can send updates, but there is only +/// one response. +#[derive(Debug, Clone, Copy)] +pub struct ClientStreaming; +impl InteractionPattern for ClientStreaming {} + +/// Defines update type and response type for a client streaming message. +pub trait ClientStreamingMsg: Msg { + /// The type for request updates + /// + /// For a request that does not support updates, this can be safely set to any type, including + /// the message type itself. Any update for such a request will result in an error. + type Update: Into + TryFrom + Send + 'static; + + /// The type for the response + /// + /// For requests that can produce errors, this can be set to [Result](std::result::Result). + type Response: Into + TryFrom + Send + 'static; +} + +/// Server error when accepting a client streaming request +#[derive(Debug)] +pub enum Error { + /// Unable to open a substream at all + Open(C::OpenError), + /// Unable to send the request to the server + Send(C::SendError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for Error {} + +/// Server error when receiving an item for a client streaming request +#[derive(Debug)] +pub enum ItemError { + /// Connection was closed before receiving the first message + EarlyClose, + /// Unable to receive the response from the server + RecvError(C::RecvError), + /// Unexpected response from the server + DowncastError, +} + +impl fmt::Display for ItemError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for ItemError {} + +impl RpcClient +where + S: Service, + C: ServiceConnection, + SInner: Service, +{ + /// Call to the server that allows the client to stream, single response + pub async fn client_streaming( + &self, + msg: M, + ) -> result::Result< + ( + UpdateSink, + BoxFuture<'static, result::Result>>, + ), + Error, + > + where + M: ClientStreamingMsg, + { + let msg = self.map.req_into_outer(msg.into()); + let (mut send, mut recv) = self.source.open_bi().await.map_err(Error::Open)?; + send.send(msg).map_err(Error::Send).await?; + let send = UpdateSink::(send, PhantomData, Arc::clone(&self.map)); + let map = Arc::clone(&self.map); + let recv = async move { + let item = recv.next().await.ok_or(ItemError::EarlyClose)?; + + match item { + Ok(x) => { + let x = map + .res_try_into_inner(x) + .map_err(|_| ItemError::DowncastError)?; + M::Response::try_from(x).map_err(|_| ItemError::DowncastError) + } + Err(e) => Err(ItemError::RecvError(e)), + } + } + .boxed(); + Ok((send, recv)) + } +} + +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, + SInner: Service, +{ + /// handle the message M using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + pub async fn client_streaming( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: ClientStreamingMsg, + F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, + Fut: Future + Send + 'static, + T: Send + 'static, + { + let Self { mut send, recv, .. } = self; + let (updates, read_error) = UpdateStream::new(recv, Arc::clone(&self.map)); + race2(read_error.map(Err), async move { + // get the response + let res = f(target, req, updates).await; + // turn into a S::Res so we can send it + let res = self.map.res_into_outer(res.into()); + // send it and return the error if any + send.send(res).await.map_err(RpcServerError::SendError) + }) + .await + } +} diff --git a/src/pattern/mod.rs b/src/pattern/mod.rs index 5826152..975be43 100644 --- a/src/pattern/mod.rs +++ b/src/pattern/mod.rs @@ -1,2 +1,6 @@ //! +pub mod bidi_streaming; +pub mod client_streaming; +pub mod rpc; +pub mod server_streaming; pub mod try_server_streaming; diff --git a/src/pattern/rpc.rs b/src/pattern/rpc.rs new file mode 100644 index 0000000..2899edf --- /dev/null +++ b/src/pattern/rpc.rs @@ -0,0 +1,158 @@ +//! +use futures::{Future, FutureExt, SinkExt, StreamExt}; + +use crate::{ + message::{InteractionPattern, Msg}, + server::{race2, RpcChannel, RpcServerError}, + transport::ConnectionErrors, + RpcClient, Service, ServiceConnection, ServiceEndpoint, +}; + +use std::{ + error, + fmt::{self, Debug}, + result, +}; + +/// Rpc interaction pattern +/// +/// There is only one request and one response. +#[derive(Debug, Clone, Copy)] +pub struct Rpc; +impl InteractionPattern for Rpc {} + +/// Defines the response type for a rpc message. +/// +/// Since this is the most common interaction pattern, this also implements [Msg] for you +/// automatically, with the interaction pattern set to [Rpc]. This is to reduce boilerplate +/// when defining rpc messages. +pub trait RpcMsg: Msg { + /// The type for the response + /// + /// For requests that can produce errors, this can be set to [Result](std::result::Result). + type Response: Into + TryFrom + Send + 'static; +} + +/// We can only do this for one trait, so we do it for RpcMsg since it is the most common +impl, S: Service> Msg for T { + type Pattern = Rpc; +} +/// Client error. All client DSL methods return a `Result` with this error type. +#[derive(Debug)] +pub enum Error { + /// Unable to open a substream at all + Open(C::OpenError), + /// Unable to send the request to the server + Send(C::SendError), + /// Server closed the stream before sending a response + EarlyClose, + /// Unable to receive the response from the server + RecvError(C::RecvError), + /// Unexpected response from the server + DowncastError, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for Error {} + +impl RpcClient +where + S: Service, + C: ServiceConnection, + SInner: Service, +{ + /// RPC call to the server, single request, single response + pub async fn rpc(&self, msg: M) -> result::Result> + where + M: RpcMsg, + { + let msg = self.map.req_into_outer(msg.into()); + let (mut send, mut recv) = self.source.open_bi().await.map_err(Error::Open)?; + send.send(msg).await.map_err(Error::::Send)?; + let res = recv + .next() + .await + .ok_or(Error::::EarlyClose)? + .map_err(Error::::RecvError)?; + // keep send alive until we have the answer + drop(send); + let res = self + .map + .res_try_into_inner(res) + .map_err(|_| Error::DowncastError)?; + M::Response::try_from(res).map_err(|_| Error::DowncastError) + } +} + +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, + SInner: Service, +{ + /// handle the message of type `M` using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + pub async fn rpc( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: RpcMsg, + F: FnOnce(T, M) -> Fut, + Fut: Future, + T: Send + 'static, + { + let Self { + mut send, mut recv, .. + } = self; + // cancel if we get an update, no matter what it is + let cancel = recv + .next() + .map(|_| RpcServerError::UnexpectedUpdateMessage::); + // race the computation and the cancellation + race2(cancel.map(Err), async move { + // get the response + let res = f(target, req).await; + // turn into a S::Res so we can send it + let res = self.map.res_into_outer(res.into()); + // send it and return the error if any + send.send(res).await.map_err(RpcServerError::SendError) + }) + .await + } + + /// A rpc call that also maps the error from the user type to the wire type + /// + /// This is useful if you want to write your function with a convenient error type like anyhow::Error, + /// yet still use a serializable error type on the wire. + pub async fn rpc_map_err( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: RpcMsg>, + F: FnOnce(T, M) -> Fut, + Fut: Future>, + E2: From, + T: Send + 'static, + { + let fut = |target: T, msg: M| async move { + // call the inner fn + let res: Result = f(target, msg).await; + // convert the error type + let res: Result = res.map_err(E2::from); + res + }; + self.rpc(req, target, fut).await + } +} diff --git a/src/pattern/server_streaming.rs b/src/pattern/server_streaming.rs new file mode 100644 index 0000000..2422468 --- /dev/null +++ b/src/pattern/server_streaming.rs @@ -0,0 +1,146 @@ +//! +use futures::{FutureExt, SinkExt, Stream, StreamExt, TryFutureExt}; + +use crate::{ + client::{BoxStreamSync, DeferDrop}, + message::{InteractionPattern, Msg}, + server::{race2, RpcChannel, RpcServerError}, + transport::ConnectionErrors, + RpcClient, Service, ServiceConnection, ServiceEndpoint, +}; + +use std::{ + error, + fmt::{self, Debug}, + result, + sync::Arc, +}; + +/// Server streaming interaction pattern +/// +/// After the initial request, the server can send a stream of responses. +#[derive(Debug, Clone, Copy)] +pub struct ServerStreaming; +impl InteractionPattern for ServerStreaming {} + +/// Defines response type for a server streaming message. +pub trait ServerStreamingMsg: Msg { + /// The type for the response + /// + /// For requests that can produce errors, this can be set to [Result](std::result::Result). + type Response: Into + TryFrom + Send + 'static; +} + +/// Server error when accepting a server streaming request +#[derive(Debug)] +pub enum Error { + /// Unable to open a substream at all + Open(C::OpenError), + /// Unable to send the request to the server + Send(C::SendError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for Error {} + +/// Client error when handling responses from a server streaming request +#[derive(Debug)] +pub enum ItemError { + /// Unable to receive the response from the server + RecvError(S::RecvError), + /// Unexpected response from the server + DowncastError, +} + +impl fmt::Display for ItemError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl error::Error for ItemError {} + +impl RpcClient +where + S: Service, + C: ServiceConnection, + SInner: Service, +{ + /// Bidi call to the server, request opens a stream, response is a stream + pub async fn server_streaming( + &self, + msg: M, + ) -> result::Result>>, Error> + where + M: ServerStreamingMsg, + { + let msg = self.map.req_into_outer(msg.into()); + let (mut send, recv) = self.source.open_bi().await.map_err(Error::Open)?; + send.send(msg).map_err(Error::::Send).await?; + let map = Arc::clone(&self.map); + let recv = recv.map(move |x| match x { + Ok(x) => { + let x = map + .res_try_into_inner(x) + .map_err(|_| ItemError::DowncastError)?; + M::Response::try_from(x).map_err(|_| ItemError::DowncastError) + } + Err(e) => Err(ItemError::RecvError(e)), + }); + // keep send alive so the request on the server side does not get cancelled + let recv = Box::pin(DeferDrop(recv, send)); + Ok(recv) + } +} + +impl RpcChannel +where + S: Service, + C: ServiceEndpoint, + SInner: Service, +{ + /// handle the message M using the given function on the target object + /// + /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. + pub async fn server_streaming( + self, + req: M, + target: T, + f: F, + ) -> result::Result<(), RpcServerError> + where + M: ServerStreamingMsg, + F: FnOnce(T, M) -> Str + Send + 'static, + Str: Stream + Send + 'static, + T: Send + 'static, + { + let Self { + mut send, mut recv, .. + } = self; + // cancel if we get an update, no matter what it is + let cancel = recv + .next() + .map(|_| RpcServerError::UnexpectedUpdateMessage::); + // race the computation and the cancellation + race2(cancel.map(Err), async move { + // get the response + let responses = f(target, req); + tokio::pin!(responses); + while let Some(response) = responses.next().await { + // turn into a S::Res so we can send it + let response = self.map.res_into_outer(response.into()); + // send it and return the error if any + send.send(response) + .await + .map_err(RpcServerError::SendError)?; + } + Ok(()) + }) + .await + } +} diff --git a/src/pattern/try_server_streaming.rs b/src/pattern/try_server_streaming.rs index f2e9d52..7a626f0 100644 --- a/src/pattern/try_server_streaming.rs +++ b/src/pattern/try_server_streaming.rs @@ -17,7 +17,10 @@ use std::{ sync::Arc, }; +/// A guard message to indicate that the stream has been created. /// +/// This is so we can dinstinguish between an error creating the stream and +/// an error in the first item produced by the stream. #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct StreamCreated; @@ -27,11 +30,11 @@ pub struct TryServerStreaming; impl InteractionPattern for TryServerStreaming {} -/// Same as [ServerStreamingMsg], but with lazy stream creation and the error type explicitly defined. +/// Same as ServerStreamingMsg, but with lazy stream creation and the error type explicitly defined. pub trait TryServerStreamingMsg: Msg where - std::result::Result: Into + TryFrom, - std::result::Result: Into + TryFrom, + result::Result: Into + TryFrom, + result::Result: Into + TryFrom, { /// Error when creating the stream type CreateError: Debug + Send + 'static; @@ -39,9 +42,7 @@ where /// Error for stream items type ItemError: Debug + Send + 'static; - /// The type for the response - /// - /// For requests that can produce errors, this can be set to [Result](std::result::Result). + /// Successful response item type Item: Send + 'static; } @@ -74,7 +75,9 @@ impl fmt::Display for Error { impl error::Error for Error {} -/// Client error when handling responses from a server streaming request +/// Client error when handling responses from a server streaming request. +/// +/// This combines network errors with application errors. #[derive(Debug)] pub enum ItemError { /// Unable to receive the response from the server diff --git a/src/server.rs b/src/server.rs index 0b71b4e..236555b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -3,11 +3,10 @@ //! The main entry point is [RpcServer] use crate::{ map::{ChainedMapper, MapService, Mapper}, - message::{BidiStreamingMsg, ClientStreamingMsg, RpcMsg, ServerStreamingMsg}, transport::ConnectionErrors, Service, ServiceEndpoint, }; -use futures::{channel::oneshot, task, task::Poll, Future, FutureExt, SinkExt, Stream, StreamExt}; +use futures::{channel::oneshot, task, task::Poll, Future, FutureExt, Stream, StreamExt}; use pin_project::pin_project; use std::{ error, @@ -114,170 +113,6 @@ where map: Arc::new(map), } } - - /// handle the message of type `M` using the given function on the target object - /// - /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn rpc( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: RpcMsg, - F: FnOnce(T, M) -> Fut, - Fut: Future, - T: Send + 'static, - { - let Self { - mut send, mut recv, .. - } = self; - // cancel if we get an update, no matter what it is - let cancel = recv - .next() - .map(|_| RpcServerError::UnexpectedUpdateMessage::); - // race the computation and the cancellation - race2(cancel.map(Err), async move { - // get the response - let res = f(target, req).await; - // turn into a S::Res so we can send it - let res = self.map.res_into_outer(res.into()); - // send it and return the error if any - send.send(res).await.map_err(RpcServerError::SendError) - }) - .await - } - - /// handle the message M using the given function on the target object - /// - /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn client_streaming( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: ClientStreamingMsg, - F: FnOnce(T, M, UpdateStream) -> Fut + Send + 'static, - Fut: Future + Send + 'static, - T: Send + 'static, - { - let Self { mut send, recv, .. } = self; - let (updates, read_error) = UpdateStream::new(recv, Arc::clone(&self.map)); - race2(read_error.map(Err), async move { - // get the response - let res = f(target, req, updates).await; - // turn into a S::Res so we can send it - let res = self.map.res_into_outer(res.into()); - // send it and return the error if any - send.send(res).await.map_err(RpcServerError::SendError) - }) - .await - } - - /// handle the message M using the given function on the target object - /// - /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn bidi_streaming( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: BidiStreamingMsg, - F: FnOnce(T, M, UpdateStream) -> Str + Send + 'static, - Str: Stream + Send + 'static, - T: Send + 'static, - { - let Self { mut send, recv, .. } = self; - // downcast the updates - let (updates, read_error) = UpdateStream::new(recv, Arc::clone(&self.map)); - // get the response - let responses = f(target, req, updates); - race2(read_error.map(Err), async move { - tokio::pin!(responses); - while let Some(response) = responses.next().await { - // turn into a S::Res so we can send it - let response = self.map.res_into_outer(response.into()); - // send it and return the error if any - send.send(response) - .await - .map_err(RpcServerError::SendError)?; - } - Ok(()) - }) - .await - } - - /// handle the message M using the given function on the target object - /// - /// If you want to support concurrent requests, you need to spawn this on a tokio task yourself. - pub async fn server_streaming( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: ServerStreamingMsg, - F: FnOnce(T, M) -> Str + Send + 'static, - Str: Stream + Send + 'static, - T: Send + 'static, - { - let Self { - mut send, mut recv, .. - } = self; - // cancel if we get an update, no matter what it is - let cancel = recv - .next() - .map(|_| RpcServerError::UnexpectedUpdateMessage::); - // race the computation and the cancellation - race2(cancel.map(Err), async move { - // get the response - let responses = f(target, req); - tokio::pin!(responses); - while let Some(response) = responses.next().await { - // turn into a S::Res so we can send it - let response = self.map.res_into_outer(response.into()); - // send it and return the error if any - send.send(response) - .await - .map_err(RpcServerError::SendError)?; - } - Ok(()) - }) - .await - } - - /// A rpc call that also maps the error from the user type to the wire type - /// - /// This is useful if you want to write your function with a convenient error type like anyhow::Error, - /// yet still use a serializable error type on the wire. - pub async fn rpc_map_err( - self, - req: M, - target: T, - f: F, - ) -> result::Result<(), RpcServerError> - where - M: RpcMsg>, - F: FnOnce(T, M) -> Fut, - Fut: Future>, - E2: From, - T: Send + 'static, - { - let fut = |target: T, msg: M| async move { - // call the inner fn - let res: Result = f(target, msg).await; - // convert the error type - let res: Result = res.map_err(E2::from); - res - }; - self.rpc(req, target, fut).await - } } impl> RpcServer { @@ -344,7 +179,7 @@ where C: ServiceEndpoint, T: TryFrom, { - fn new( + pub(crate) fn new( recv: C::RecvStream, map: Arc>, ) -> (Self, UnwrapToPending>) { @@ -433,7 +268,7 @@ impl fmt::Display for RpcServerError { impl error::Error for RpcServerError {} /// Take an oneshot receiver and just return Pending the underlying future returns `Err(oneshot::Canceled)` -struct UnwrapToPending(oneshot::Receiver); +pub(crate) struct UnwrapToPending(oneshot::Receiver); impl Future for UnwrapToPending { type Output = T; diff --git a/tests/hyper.rs b/tests/hyper.rs index 7f6c3a5..0f242d3 100644 --- a/tests/hyper.rs +++ b/tests/hyper.rs @@ -5,7 +5,6 @@ use ::hyper::Uri; use derive_more::{From, TryInto}; use flume::Receiver; use quic_rpc::{ - client::RpcClientError, declare_rpc, server::RpcServerError, transport::hyper::{self, HyperConnection, HyperServerEndpoint, RecvError}, @@ -247,7 +246,9 @@ async fn hyper_channel_errors() -> anyhow::Result<()> { let res = client.rpc(BigRequest(vec![0; 20_000_000])).await; assert_matches!( res, - Err(RpcClientError::Send(hyper::SendError::SizeError(_))) + Err(quic_rpc::pattern::rpc::Error::Send( + hyper::SendError::SizeError(_) + )) ); assert_server_result!(Err(RpcServerError::EarlyClose)); @@ -255,20 +256,22 @@ async fn hyper_channel_errors() -> anyhow::Result<()> { let res = client.rpc(NoSerRequest(NoSer)).await; assert_matches!( res, - Err(RpcClientError::Send(hyper::SendError::SerializeError(_))) + Err(quic_rpc::pattern::rpc::Error::Send( + hyper::SendError::SerializeError(_) + )) ); assert_server_result!(Err(RpcServerError::EarlyClose)); // not deserializable - should fail on the server side let res = client.rpc(NoDeserRequest(NoDeser)).await; - assert_matches!(res, Err(RpcClientError::EarlyClose)); + assert_matches!(res, Err(quic_rpc::pattern::rpc::Error::EarlyClose)); assert_server_result!(Err(RpcServerError::RecvError( hyper::RecvError::DeserializeError(_) ))); // response not serializable - should fail on the server side let res = client.rpc(NoSerResponseRequest).await; - assert_matches!(res, Err(RpcClientError::EarlyClose)); + assert_matches!(res, Err(quic_rpc::pattern::rpc::Error::EarlyClose)); assert_server_result!(Err(RpcServerError::SendError( hyper::SendError::SerializeError(_) ))); @@ -277,7 +280,9 @@ async fn hyper_channel_errors() -> anyhow::Result<()> { let res = client.rpc(NoDeserResponseRequest).await; assert_matches!( res, - Err(RpcClientError::RecvError(RecvError::DeserializeError(_))) + Err(quic_rpc::pattern::rpc::Error::RecvError( + RecvError::DeserializeError(_) + )) ); assert_server_result!(Ok(())); @@ -288,7 +293,7 @@ async fn hyper_channel_errors() -> anyhow::Result<()> { // response big - should fail let res = client.rpc(BigResponseRequest(20_000_000)).await; - assert_matches!(res, Err(RpcClientError::EarlyClose)); + assert_matches!(res, Err(quic_rpc::pattern::rpc::Error::EarlyClose)); assert_server_result!(Err(RpcServerError::SendError(hyper::SendError::SizeError( _ )))); From 0338e43a43a56f8c2ea90a6e6321a4f2e25b4057 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Wed, 10 Apr 2024 14:04:33 +0300 Subject: [PATCH 35/49] add more docs about what an interaction pattern is --- src/pattern/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pattern/mod.rs b/src/pattern/mod.rs index 975be43..da2b879 100644 --- a/src/pattern/mod.rs +++ b/src/pattern/mod.rs @@ -1,4 +1,9 @@ +//! Predefined interaction patterns. //! +//! An interaction pattern can be as simple as an rpc call or something more +//! complex such as bidirectional streaming. +//! +//! Each pattern defines different associated message types for the interaction. pub mod bidi_streaming; pub mod client_streaming; pub mod rpc; From d8ad84fa8ae620acf7daa18ecc8d1ab95edc1359 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Wed, 10 Apr 2024 14:17:27 +0300 Subject: [PATCH 36/49] disable nightly tests and update derive_more --- .github/workflows/ci.yml | 3 -- Cargo.lock | 51 +++++++++++++++++++-------------- Cargo.toml | 4 +-- examples/split/types/Cargo.toml | 2 +- 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bcb304..f0c31a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,8 +51,6 @@ jobs: name: "Windows GNU" channel: - "stable" - - "beta" - - "nightly" steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@master @@ -84,7 +82,6 @@ jobs: channel: - "stable" - "beta" - - "nightly" steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@master diff --git a/Cargo.lock b/Cargo.lock index 1dea3fe..99e88e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,9 +143,12 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "core-foundation" @@ -171,15 +174,24 @@ checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" [[package]] name = "derive_more" -version = "0.99.17" +version = "1.0.0-beta.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7abbfc297053be59290e3152f8cbcd52c8642e0728b69ee187d991d4c1af08d" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0-beta.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e" dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version", - "syn 1.0.109", + "syn 2.0.28", + "unicode-xid", ] [[package]] @@ -862,15 +874,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "0.38.8" @@ -975,12 +978,6 @@ dependencies = [ "libc", ] -[[package]] -name = "semver" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" - [[package]] name = "serde" version = "1.0.183" @@ -1335,6 +1332,18 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "untrusted" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index 65f41d6..b3af8f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ futures = "0.3" hyper = { version = "0.14.16", features = ["full"], optional = true } pin-project = "1" quinn = { version = "0.10", optional = true } -serde = { version = "1.0.183" } +serde = { version = "1.0.183", features = ["derive"] } tokio = { version = "1", default-features = false, features = ["macros"] } tokio-serde = { version = "0.8", features = ["bincode"], optional = true } tokio-util = { version = "0.7", features = ["codec"], optional = true } @@ -37,7 +37,7 @@ version = "0.4.20" [dev-dependencies] anyhow = "1.0.73" async-stream = "0.3.3" -derive_more = "0.99.17" +derive_more = { version = "1.0.0-beta.1", features = ["full"] } serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } quinn = "0.10" diff --git a/examples/split/types/Cargo.toml b/examples/split/types/Cargo.toml index fd976ab..8c77aa5 100644 --- a/examples/split/types/Cargo.toml +++ b/examples/split/types/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -derive_more = "0.99.17" +derive_more = { version = "1.0.0-beta.1", features = ["from", "display", "try_into"] } futures = "0.3.26" quic-rpc = { path = "../../..", features = ["macros"] } serde = { version = "1", features = ["derive"] } From 531159bc160313afde24926dfd41ff8197c4ddfa Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 15 Apr 2024 09:53:34 +0300 Subject: [PATCH 37/49] WIP rip out futures --- Cargo.lock | 26 +++++++++++++++++++++++--- Cargo.toml | 3 ++- src/client.rs | 12 +++++++----- src/pattern/bidi_streaming.rs | 3 ++- src/pattern/client_streaming.rs | 4 ++-- src/pattern/rpc.rs | 3 ++- src/pattern/server_streaming.rs | 3 ++- src/pattern/try_server_streaming.rs | 4 ++-- src/server.rs | 5 +++-- src/transport/flume.rs | 4 +++- src/transport/misc/mod.rs | 6 ++++-- src/transport/mod.rs | 4 +++- src/transport/quinn.rs | 13 +++++++------ src/transport/util.rs | 3 ++- 14 files changed, 64 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99e88e3..85de8ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,6 +312,19 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.28" @@ -325,9 +338,9 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" @@ -632,6 +645,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.1" @@ -722,7 +741,8 @@ dependencies = [ "derive_more", "educe", "flume", - "futures", + "futures-lite", + "futures-sink", "hyper", "pin-project", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index b3af8f8..0e6443c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,8 @@ rust-version = "1.65" bincode = { version = "1.3.3", optional = true } bytes = { version = "1", optional = true } flume = { version = "0.11", optional = true } -futures = "0.3" +futures-lite = "2.3.0" +futures-sink = "0.3.30" hyper = { version = "0.14.16", features = ["full"], optional = true } pin-project = "1" quinn = { version = "0.10", optional = true } diff --git a/src/client.rs b/src/client.rs index 6db1341..537c747 100644 --- a/src/client.rs +++ b/src/client.rs @@ -5,7 +5,9 @@ use crate::{ map::{ChainedMapper, MapService, Mapper}, Service, ServiceConnection, }; -use futures::{Sink, SinkExt, Stream}; +use futures_lite::Stream; +use futures_sink::Sink; + use pin_project::pin_project; use std::{ fmt::Debug, @@ -62,20 +64,20 @@ where type Error = C::SendError; fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().0.poll_ready_unpin(cx) + self.project().0.poll_ready(cx) } fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { let req = self.2.req_into_outer(item.into()); - self.project().0.start_send_unpin(req) + self.project().0.start_send(req) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().0.poll_flush_unpin(cx) + self.project().0.poll_flush(cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().0.poll_close_unpin(cx) + self.project().0.poll_close(cx) } } diff --git a/src/pattern/bidi_streaming.rs b/src/pattern/bidi_streaming.rs index 28142a6..b1f6be7 100644 --- a/src/pattern/bidi_streaming.rs +++ b/src/pattern/bidi_streaming.rs @@ -1,5 +1,6 @@ //! -use futures::{FutureExt, SinkExt, Stream, StreamExt}; + +use futures_lite::{Stream, StreamExt}; use crate::{ client::{BoxStreamSync, UpdateSink}, diff --git a/src/pattern/client_streaming.rs b/src/pattern/client_streaming.rs index 89fc0b0..8dd8b4a 100644 --- a/src/pattern/client_streaming.rs +++ b/src/pattern/client_streaming.rs @@ -1,5 +1,5 @@ //! -use futures::{future::BoxFuture, Future, FutureExt, SinkExt, StreamExt, TryFutureExt}; +use futures_lite::{future::Boxed, Future}; use crate::{ client::UpdateSink, @@ -88,7 +88,7 @@ where ) -> result::Result< ( UpdateSink, - BoxFuture<'static, result::Result>>, + Boxed>>, ), Error, > diff --git a/src/pattern/rpc.rs b/src/pattern/rpc.rs index 2899edf..4373c63 100644 --- a/src/pattern/rpc.rs +++ b/src/pattern/rpc.rs @@ -1,5 +1,6 @@ //! -use futures::{Future, FutureExt, SinkExt, StreamExt}; + +use futures_lite::Future; use crate::{ message::{InteractionPattern, Msg}, diff --git a/src/pattern/server_streaming.rs b/src/pattern/server_streaming.rs index 2422468..0b3c5ca 100644 --- a/src/pattern/server_streaming.rs +++ b/src/pattern/server_streaming.rs @@ -1,5 +1,6 @@ //! -use futures::{FutureExt, SinkExt, Stream, StreamExt, TryFutureExt}; + +use futures_lite::Stream; use crate::{ client::{BoxStreamSync, DeferDrop}, diff --git a/src/pattern/try_server_streaming.rs b/src/pattern/try_server_streaming.rs index 7a626f0..f64f6ec 100644 --- a/src/pattern/try_server_streaming.rs +++ b/src/pattern/try_server_streaming.rs @@ -1,5 +1,5 @@ //! -use futures::{FutureExt, SinkExt, Stream, StreamExt, TryFutureExt}; +use futures_lite::{Future, Stream}; use serde::{Deserialize, Serialize}; use crate::{ @@ -120,7 +120,7 @@ where std::result::Result: Into + TryFrom, F: FnOnce(T, M) -> Fut + Send + 'static, - Fut: futures::Future> + Send + 'static, + Fut: Future> + Send + 'static, Str: Stream> + Send + 'static, T: Send + 'static, { diff --git a/src/server.rs b/src/server.rs index 236555b..2ce8065 100644 --- a/src/server.rs +++ b/src/server.rs @@ -6,15 +6,16 @@ use crate::{ transport::ConnectionErrors, Service, ServiceEndpoint, }; -use futures::{channel::oneshot, task, task::Poll, Future, FutureExt, Stream, StreamExt}; +use futures_lite::{Future, Stream}; use pin_project::pin_project; +use tokio::sync::oneshot; use std::{ error, fmt::{self, Debug}, marker::PhantomData, pin::Pin, result, - sync::Arc, + sync::Arc, task::{self, Poll}, }; /// A server channel for a specific service. diff --git a/src/transport/flume.rs b/src/transport/flume.rs index 9c86843..73aa244 100644 --- a/src/transport/flume.rs +++ b/src/transport/flume.rs @@ -1,12 +1,14 @@ //! Memory transport implementation using [flume] //! //! [flume]: https://docs.rs/flume/ +use futures_lite::{Future, Stream}; +use futures_sink::Sink; + use crate::{ transport::{Connection, ConnectionErrors, LocalAddr, ServerEndpoint}, RpcMessage, }; use core::fmt; -use futures::{Future, FutureExt, Sink, SinkExt, Stream, StreamExt}; use std::{error, fmt::Display, marker::PhantomData, pin::Pin, result, task::Poll}; use super::ConnectionCommon; diff --git a/src/transport/misc/mod.rs b/src/transport/misc/mod.rs index 598b68c..a837ae8 100644 --- a/src/transport/misc/mod.rs +++ b/src/transport/misc/mod.rs @@ -1,10 +1,12 @@ //! Miscellaneous transport utilities +use futures_lite::{future, stream}; +use futures_sink::Sink; + use crate::{ transport::{ConnectionErrors, ServerEndpoint}, RpcMessage, }; -use futures::{future, stream, Sink}; use std::convert::Infallible; use super::ConnectionCommon; @@ -31,7 +33,7 @@ impl ServerEndpoint for DummyServerEnd type AcceptBiFut = future::Pending>; fn accept_bi(&self) -> Self::AcceptBiFut { - futures::future::pending() + futures_lite::future::pending() } fn local_addr(&self) -> &[super::LocalAddr] { diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 9d03b3e..ea45aa5 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -1,6 +1,8 @@ //! Transports for quic-rpc +use futures_lite::{Future, Stream}; +use futures_sink::Sink; + use crate::RpcError; -use futures::{Future, Sink, Stream}; use std::{ fmt::{self, Debug, Display}, net::SocketAddr, diff --git a/src/transport/quinn.rs b/src/transport/quinn.rs index 101739b..700409d 100644 --- a/src/transport/quinn.rs +++ b/src/transport/quinn.rs @@ -3,11 +3,12 @@ use crate::{ transport::{Connection, ConnectionErrors, LocalAddr, ServerEndpoint}, RpcMessage, }; -use futures::channel::oneshot; -use futures::{Future, FutureExt, Sink, SinkExt, Stream, StreamExt}; +use futures_lite::{future, Future, Stream}; +use futures_sink::Sink; use pin_project::pin_project; use serde::de::DeserializeOwned; use serde::Serialize; +use tokio::sync::oneshot; use std::net::SocketAddr; use std::sync::Arc; use std::task::{Context, Poll}; @@ -307,7 +308,7 @@ impl QuinnConnection { addr, name, }; - futures::pin_mut!(reconnect); + tokio::pin!(reconnect); let mut receiver = Receiver::new(&requests); @@ -320,8 +321,8 @@ impl QuinnConnection { let mut conn_result = None; let mut chann_result = None; if !reconnect.connected() && pending_request.is_none() { - match futures::future::select(reconnect.as_mut(), receiver.next()).await { - futures::future::Either::Left((connection_result, _)) => { + match future::select(reconnect.as_mut(), receiver.next()).await { + ::future::Either::Left((connection_result, _)) => { conn_result = Some(connection_result) } futures::future::Either::Right((channel_result, _)) => { @@ -554,7 +555,7 @@ impl<'a, T> Receiver<'a, T> { } } -impl<'a, T> futures::stream::Stream for Receiver<'a, T> { +impl<'a, T> Stream for Receiver<'a, T> { type Item = T; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { diff --git a/src/transport/util.rs b/src/transport/util.rs index e477830..2646ae2 100644 --- a/src/transport/util.rs +++ b/src/transport/util.rs @@ -4,7 +4,8 @@ use std::{ }; use bincode::Options; -use futures::{Sink, SinkExt, Stream, StreamExt}; +use futures_lite::Stream; +use futures_sink::Sink; use pin_project::pin_project; use serde::{de::DeserializeOwned, Serialize}; use tokio::io::{AsyncRead, AsyncWrite}; From fe72fb02921512e7015d520f58ea9d70f509330d Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 22 Apr 2024 10:45:40 +0200 Subject: [PATCH 38/49] update the rest --- Cargo.lock | 37 ++++++++++++++++-------- Cargo.toml | 5 ++-- examples/macro.rs | 3 +- examples/modularize.rs | 10 ++++--- examples/store.rs | 3 +- src/pattern/bidi_streaming.rs | 1 + src/pattern/client_streaming.rs | 3 +- src/pattern/rpc.rs | 3 +- src/pattern/server_streaming.rs | 3 +- src/pattern/try_server_streaming.rs | 3 +- src/server.rs | 11 ++++---- src/transport/combined.rs | 17 ++++++----- src/transport/flume.rs | 22 +++++++-------- src/transport/hyper.rs | 34 +++++++++++----------- src/transport/quinn.rs | 44 ++++++++++++++++++----------- src/transport/util.rs | 10 +++---- tests/math.rs | 24 ++++++++-------- tests/slow_math.rs | 2 +- tests/try.rs | 2 +- 19 files changed, 137 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85de8ee..be5e76d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,11 +279,22 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-buffered" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de8419e65098e54c06f5ae8a130a79e8ba2e391ff995d260ca5d77ea72ab2fe3" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", +] + [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -291,9 +302,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" @@ -308,9 +319,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" @@ -327,9 +338,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", @@ -344,15 +355,15 @@ checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -741,8 +752,10 @@ dependencies = [ "derive_more", "educe", "flume", + "futures-buffered", "futures-lite", "futures-sink", + "futures-util", "hyper", "pin-project", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 0e6443c..c7e8cb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ bytes = { version = "1", optional = true } flume = { version = "0.11", optional = true } futures-lite = "2.3.0" futures-sink = "0.3.30" +futures-util = { version = "0.3.30", features = ["sink"] } hyper = { version = "0.14.16", features = ["full"], optional = true } pin-project = "1" quinn = { version = "0.10", optional = true } @@ -48,6 +49,7 @@ thousands = "0.2.0" tracing-subscriber = "0.3.16" tempfile = "3.5.0" proc-macro2 = "1.0.66" +futures-buffered = "0.2.4" [features] hyper-transport = ["flume", "hyper", "bincode", "bytes"] @@ -55,7 +57,7 @@ quinn-transport = ["flume", "quinn", "bincode", "tokio-serde", "tokio-util"] flume-transport = ["flume"] combined-transport = [] macros = [] -default = ["flume-transport"] +default = ["flume-transport", "quinn-transport", "flume-transport", "hyper-transport", "macros", "combined-transport"] [package.metadata.docs.rs] all-features = true @@ -78,4 +80,3 @@ required-features = ["flume-transport"] [workspace] members = ["examples/split/types", "examples/split/server", "examples/split/client"] - diff --git a/examples/macro.rs b/examples/macro.rs index 1e46013..41ed7a3 100644 --- a/examples/macro.rs +++ b/examples/macro.rs @@ -49,7 +49,8 @@ mod store_rpc { } use async_stream::stream; -use futures::{SinkExt, Stream, StreamExt}; +use futures_lite::{Stream, StreamExt}; +use futures_util::SinkExt; use quic_rpc::client::RpcClient; use quic_rpc::server::run_server_loop; use quic_rpc::transport::flume; diff --git a/examples/modularize.rs b/examples/modularize.rs index ce3a72e..05aa8f2 100644 --- a/examples/modularize.rs +++ b/examples/modularize.rs @@ -8,7 +8,8 @@ //! unchanged. use anyhow::Result; -use futures::{SinkExt, TryStreamExt}; +use futures_lite::StreamExt; +use futures_util::SinkExt; use quic_rpc::{transport::flume, RpcClient, RpcServer, ServiceConnection, ServiceEndpoint}; use tracing::warn; @@ -272,7 +273,7 @@ mod calc { use anyhow::{bail, Result}; use derive_more::{From, TryInto}; - use futures::{Stream, StreamExt}; + use futures_lite::{Stream, StreamExt}; use quic_rpc::{ message::{ClientStreaming, ClientStreamingMsg, Msg, RpcMsg}, server::RpcChannel, @@ -394,7 +395,8 @@ mod clock { use anyhow::Result; use derive_more::{From, TryInto}; - use futures::{stream::BoxStream, Stream, StreamExt, TryStreamExt}; + use futures_lite::{stream::Boxed as BoxStream, Stream, StreamExt}; + use futures_util::TryStreamExt; use quic_rpc::{ message::{Msg, ServerStreaming, ServerStreamingMsg}, server::RpcChannel, @@ -524,7 +526,7 @@ mod clock { pub fn new(client: RpcClient) -> Self { Self { client } } - pub async fn tick(&self) -> Result>> { + pub async fn tick(&self) -> Result>> { let res = self.client.server_streaming(TickRequest).await?; Ok(res.map_ok(|r| r.tick).map_err(anyhow::Error::from).boxed()) } diff --git a/examples/store.rs b/examples/store.rs index 19c9ab9..1042a6b 100644 --- a/examples/store.rs +++ b/examples/store.rs @@ -1,7 +1,8 @@ #![allow(clippy::enum_variant_names)] use async_stream::stream; use derive_more::{From, TryInto}; -use futures::{SinkExt, Stream, StreamExt}; +use futures_lite::{Stream, StreamExt}; +use futures_util::SinkExt; use quic_rpc::{ server::RpcServerError, transport::{flume, Connection, ServerEndpoint}, diff --git a/src/pattern/bidi_streaming.rs b/src/pattern/bidi_streaming.rs index b1f6be7..29d4c66 100644 --- a/src/pattern/bidi_streaming.rs +++ b/src/pattern/bidi_streaming.rs @@ -1,6 +1,7 @@ //! use futures_lite::{Stream, StreamExt}; +use futures_util::{FutureExt, SinkExt}; use crate::{ client::{BoxStreamSync, UpdateSink}, diff --git a/src/pattern/client_streaming.rs b/src/pattern/client_streaming.rs index 8dd8b4a..82000ad 100644 --- a/src/pattern/client_streaming.rs +++ b/src/pattern/client_streaming.rs @@ -1,5 +1,6 @@ //! -use futures_lite::{future::Boxed, Future}; +use futures_lite::{future::Boxed, Future, StreamExt}; +use futures_util::{FutureExt, SinkExt, TryFutureExt}; use crate::{ client::UpdateSink, diff --git a/src/pattern/rpc.rs b/src/pattern/rpc.rs index 4373c63..d9828f2 100644 --- a/src/pattern/rpc.rs +++ b/src/pattern/rpc.rs @@ -1,6 +1,7 @@ //! -use futures_lite::Future; +use futures_lite::{Future, StreamExt}; +use futures_util::{FutureExt, SinkExt}; use crate::{ message::{InteractionPattern, Msg}, diff --git a/src/pattern/server_streaming.rs b/src/pattern/server_streaming.rs index 0b3c5ca..d3d76b2 100644 --- a/src/pattern/server_streaming.rs +++ b/src/pattern/server_streaming.rs @@ -1,6 +1,7 @@ //! -use futures_lite::Stream; +use futures_lite::{Stream, StreamExt}; +use futures_util::{FutureExt, SinkExt, TryFutureExt}; use crate::{ client::{BoxStreamSync, DeferDrop}, diff --git a/src/pattern/try_server_streaming.rs b/src/pattern/try_server_streaming.rs index f64f6ec..89fe82b 100644 --- a/src/pattern/try_server_streaming.rs +++ b/src/pattern/try_server_streaming.rs @@ -1,5 +1,6 @@ //! -use futures_lite::{Future, Stream}; +use futures_lite::{Future, Stream, StreamExt}; +use futures_util::{FutureExt, SinkExt, TryFutureExt}; use serde::{Deserialize, Serialize}; use crate::{ diff --git a/src/server.rs b/src/server.rs index 2ce8065..0e4e522 100644 --- a/src/server.rs +++ b/src/server.rs @@ -6,17 +6,18 @@ use crate::{ transport::ConnectionErrors, Service, ServiceEndpoint, }; -use futures_lite::{Future, Stream}; +use futures_lite::{Future, Stream, StreamExt}; use pin_project::pin_project; -use tokio::sync::oneshot; use std::{ error, fmt::{self, Debug}, marker::PhantomData, pin::Pin, result, - sync::Arc, task::{self, Poll}, + sync::Arc, + task::{self, Poll}, }; +use tokio::sync::oneshot; /// A server channel for a specific service. /// @@ -201,7 +202,7 @@ where fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { let mut this = self.project(); - match this.0.poll_next_unpin(cx) { + match Pin::new(&mut this.0).poll_next(cx) { Poll::Ready(Some(msg)) => match msg { Ok(msg) => { let msg = this.3.req_try_into_inner(msg); @@ -275,7 +276,7 @@ impl Future for UnwrapToPending { type Output = T; fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { - match self.0.poll_unpin(cx) { + match Pin::new(&mut self.0).poll(cx) { Poll::Ready(Ok(x)) => Poll::Ready(x), Poll::Ready(Err(_)) => Poll::Pending, Poll::Pending => Poll::Pending, diff --git a/src/transport/combined.rs b/src/transport/combined.rs index df0cedb..108dc36 100644 --- a/src/transport/combined.rs +++ b/src/transport/combined.rs @@ -1,10 +1,9 @@ //! Transport that combines two other transports use super::{Connection, ConnectionCommon, ConnectionErrors, LocalAddr, ServerEndpoint}; use crate::RpcMessage; -use futures::{ - future::{self, BoxFuture}, - FutureExt, Sink, Stream, TryFutureExt, -}; +use futures_lite::{future::Boxed as BoxFuture, Stream}; +use futures_sink::Sink; +use futures_util::{FutureExt, TryFutureExt}; use pin_project::pin_project; use std::{ error, fmt, @@ -284,11 +283,11 @@ impl error::Error for AcceptBiError = - BoxFuture<'static, result::Result, self::OpenBiError>>; + BoxFuture, self::OpenBiError>>; /// Future returned by accept_bi pub type AcceptBiFuture = - BoxFuture<'static, result::Result, self::AcceptBiError>>; + BoxFuture, self::AcceptBiError>>; type Socket = ( self::SendSink, @@ -324,7 +323,7 @@ impl, B: Connection, In: RpcMessage, Out: RpcMes let (send, recv) = b.open_bi().await.map_err(OpenBiError::B)?; Ok((SendSink::B(send), RecvStream::B(recv))) } else { - future::err(OpenBiError::NoChannel).await + std::future::ready(Err(OpenBiError::NoChannel)).await } } .boxed() @@ -363,7 +362,7 @@ impl, B: ServerEndpoint, In: RpcMessage, Out .map_err(AcceptBiError::A) .left_future() } else { - future::pending().right_future() + std::future::pending().right_future() }; let b_fut = if let Some(b) = &self.b { b.accept_bi() @@ -376,7 +375,7 @@ impl, B: ServerEndpoint, In: RpcMessage, Out .map_err(AcceptBiError::B) .left_future() } else { - future::pending().right_future() + std::future::pending().right_future() }; async move { tokio::select! { diff --git a/src/transport/flume.rs b/src/transport/flume.rs index 73aa244..f77159e 100644 --- a/src/transport/flume.rs +++ b/src/transport/flume.rs @@ -41,14 +41,14 @@ impl Sink for SendSink { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.0 - .poll_ready_unpin(cx) + Pin::new(&mut self.0) + .poll_ready(cx) .map_err(|_| SendError::ReceiverDropped) } fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { - self.0 - .start_send_unpin(item) + Pin::new(&mut self.0) + .start_send(item) .map_err(|_| SendError::ReceiverDropped) } @@ -56,8 +56,8 @@ impl Sink for SendSink { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.0 - .poll_flush_unpin(cx) + Pin::new(&mut self.0) + .poll_flush(cx) .map_err(|_| SendError::ReceiverDropped) } @@ -65,8 +65,8 @@ impl Sink for SendSink { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.0 - .poll_close_unpin(cx) + Pin::new(&mut self.0) + .poll_close(cx) .map_err(|_| SendError::ReceiverDropped) } } @@ -87,7 +87,7 @@ impl Stream for RecvStream { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - match self.0.poll_next_unpin(cx) { + match Pin::new(&mut self.0).poll_next(cx) { Poll::Ready(Some(v)) => Poll::Ready(Some(Ok(v))), Poll::Ready(None) => Poll::Ready(None), Poll::Pending => Poll::Pending, @@ -158,7 +158,7 @@ impl Future for OpenBiFuture { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll { - match self.inner.poll_unpin(cx) { + match Pin::new(&mut self.inner).poll(cx) { Poll::Ready(Ok(())) => self .res .take() @@ -186,7 +186,7 @@ impl Future for AcceptBiFuture { type Output = result::Result<(SendSink, RecvStream), AcceptBiError>; fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { - match self.wrapped.poll_unpin(cx) { + match Pin::new(&mut self.wrapped).poll(cx) { Poll::Ready(Ok((send, recv))) => Poll::Ready(Ok((send, recv))), Poll::Ready(Err(_)) => Poll::Ready(Err(AcceptBiError::RemoteDropped)), Poll::Pending => Poll::Pending, diff --git a/src/transport/hyper.rs b/src/transport/hyper.rs index 5b28024..4d47eff 100644 --- a/src/transport/hyper.rs +++ b/src/transport/hyper.rs @@ -2,15 +2,17 @@ //! //! [hyper]: https://crates.io/crates/hyper/ use std::{ - convert::Infallible, error, fmt, io, marker::PhantomData, net::SocketAddr, pin::Pin, result, - sync::Arc, task::Poll, + convert::Infallible, error, fmt, future::Future, io, marker::PhantomData, net::SocketAddr, + pin::Pin, result, sync::Arc, task::Poll, }; use crate::transport::{Connection, ConnectionErrors, LocalAddr, ServerEndpoint}; use crate::RpcMessage; use bytes::Bytes; use flume::{r#async::RecvFut, Receiver, Sender}; -use futures::{future::FusedFuture, Future, FutureExt, Sink, SinkExt, StreamExt}; +use futures_lite::{Stream, StreamExt}; +use futures_sink::Sink; +use futures_util::future::FusedFuture; use hyper::{ client::{connect::Connect, HttpConnector, ResponseFuture}, server::conn::{AddrIncoming, AddrStream}, @@ -411,14 +413,14 @@ impl Clone for RecvStream { } } -impl futures::Stream for RecvStream { +impl Stream for RecvStream { type Item = Result; fn poll_next( mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.recv.poll_next_unpin(cx) + Pin::new(&mut self.recv).poll_next(cx) } } @@ -466,8 +468,8 @@ impl Sink for SendSink { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.sink - .poll_ready_unpin(cx) + Pin::new(&mut self.sink) + .poll_ready(cx) .map_err(|_| SendError::ReceiverDropped) } @@ -481,8 +483,8 @@ impl Sink for SendSink { ), }; // attempt sending - self.sink - .start_send_unpin(send) + Pin::new(&mut self.sink) + .start_send(send) .map_err(|_| SendError::ReceiverDropped)?; res } @@ -491,8 +493,8 @@ impl Sink for SendSink { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.sink - .poll_flush_unpin(cx) + Pin::new(&mut self.sink) + .poll_flush(cx) .map_err(|_| SendError::ReceiverDropped) } @@ -500,8 +502,8 @@ impl Sink for SendSink { mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { - self.sink - .poll_close_unpin(cx) + Pin::new(&mut self.sink) + .poll_close(cx) .map_err(|_| SendError::ReceiverDropped) } } @@ -606,7 +608,7 @@ impl Future for OpenBiFuture { ) -> std::task::Poll { let this = self.project(); match this.chan { - Some(Ok((fut, _, _))) => match fut.poll_unpin(cx) { + Some(Ok((fut, _, _))) => match Pin::new(fut).poll(cx) { Poll::Ready(Ok(res)) => { event!(Level::TRACE, "OpenBiFuture got response"); let (_, out_tx, config) = this.chan.take().unwrap().unwrap(); @@ -697,12 +699,12 @@ impl Future for AcceptBiFuture { type Output = result::Result, AcceptBiError>; fn poll( - self: std::pin::Pin<&mut Self>, + self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll { let this = self.project(); match this.chan { - Some((fut, _)) => match fut.poll_unpin(cx) { + Some((fut, _)) => match Pin::new(fut).poll(cx) { Poll::Ready(Ok((recv, send))) => { let (_, config) = this.chan.take().unwrap(); Poll::Ready(Ok(( diff --git a/src/transport/quinn.rs b/src/transport/quinn.rs index 700409d..0accbb5 100644 --- a/src/transport/quinn.rs +++ b/src/transport/quinn.rs @@ -3,16 +3,17 @@ use crate::{ transport::{Connection, ConnectionErrors, LocalAddr, ServerEndpoint}, RpcMessage, }; -use futures_lite::{future, Future, Stream}; +use futures_lite::{Future, Stream, StreamExt}; use futures_sink::Sink; +use futures_util::FutureExt; use pin_project::pin_project; use serde::de::DeserializeOwned; use serde::Serialize; -use tokio::sync::oneshot; use std::net::SocketAddr; use std::sync::Arc; use std::task::{Context, Poll}; use std::{fmt, io, marker::PhantomData, pin::Pin, result}; +use tokio::sync::oneshot; use tracing::{debug_span, Instrument}; use super::{ @@ -317,15 +318,23 @@ impl QuinnConnection { > = None; let mut connection = None; + enum Racer { + Reconnect(Result), + Channel(Option>>), + } + loop { let mut conn_result = None; let mut chann_result = None; if !reconnect.connected() && pending_request.is_none() { - match future::select(reconnect.as_mut(), receiver.next()).await { - ::future::Either::Left((connection_result, _)) => { - conn_result = Some(connection_result) - } - futures::future::Either::Right((channel_result, _)) => { + match futures_lite::future::race( + reconnect.as_mut().map(Racer::Reconnect), + receiver.next().map(Racer::Channel), + ) + .await + { + Racer::Reconnect(connection_result) => conn_result = Some(connection_result), + Racer::Channel(channel_result) => { chann_result = Some(channel_result); } } @@ -507,7 +516,8 @@ impl Future for ReconnectHandler { Poll::Ready(Err(ReconnectErr::Connect(e))) } }, - ConnectionState::Connecting(mut connecting) => match connecting.poll_unpin(cx) { + ConnectionState::Connecting(mut connecting) => match Pin::new(&mut connecting).poll(cx) + { Poll::Ready(res) => match res { Ok(connection) => { self.state = ConnectionState::Connected(connection.clone()); @@ -565,7 +575,7 @@ impl<'a, T> Stream for Receiver<'a, T> { *self = Receiver::Receiving(recv, fut); self.poll_next(cx) } - Receiver::Receiving(recv, mut fut) => match fut.poll_unpin(cx) { + Receiver::Receiving(recv, mut fut) => match Pin::new(&mut fut).poll(cx) { Poll::Ready(Ok(t)) => { *self = Receiver::PreReceive(recv); Poll::Ready(Some(t)) @@ -661,25 +671,25 @@ impl Sink for SendSink { self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - self.project().0.poll_ready_unpin(cx) + Pin::new(&mut self.project().0).poll_ready(cx) } fn start_send(self: Pin<&mut Self>, item: Out) -> Result<(), Self::Error> { - self.project().0.start_send_unpin(item) + Pin::new(&mut self.project().0).start_send(item) } fn poll_flush( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - self.project().0.poll_flush_unpin(cx) + Pin::new(&mut self.project().0).poll_flush(cx) } fn poll_close( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - self.project().0.poll_close_unpin(cx) + Pin::new(&mut self.project().0).poll_close(cx) } } @@ -718,7 +728,7 @@ impl Stream for RecvStream { self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll> { - self.project().0.poll_next_unpin(cx) + Pin::new(&mut self.project().0).poll_next(cx) } } @@ -766,7 +776,7 @@ impl Future for OpenBiFuture { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.0.take() { - OpenBiFutureState::Sending(mut fut, recever) => match fut.poll_unpin(cx) { + OpenBiFutureState::Sending(mut fut, recever) => match Pin::new(&mut fut).poll(cx) { Poll::Ready(Ok(_)) => { self.0 = OpenBiFutureState::Receiving(recever); self.poll(cx) @@ -777,7 +787,7 @@ impl Future for OpenBiFuture { } Poll::Ready(Err(_)) => Poll::Ready(Err(quinn::ConnectionError::LocallyClosed)), }, - OpenBiFutureState::Receiving(mut fut) => match fut.poll_unpin(cx) { + OpenBiFutureState::Receiving(mut fut) => match Pin::new(&mut fut).poll(cx) { Poll::Ready(Ok(Ok((send, recv)))) => { let send = SendSink::new(send); let recv = RecvStream::new(recv); @@ -815,7 +825,7 @@ impl Future for AcceptBiFuture { self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll { - self.project().0.poll_unpin(cx).map(|conn| { + Pin::new(&mut self.project().0).poll(cx).map(|conn| { let (send, recv) = conn.map_err(|e| { tracing::warn!("accept_bi: error receiving connection: {}", e); quinn::ConnectionError::LocallyClosed diff --git a/src/transport/util.rs b/src/transport/util.rs index 2646ae2..3c4073b 100644 --- a/src/transport/util.rs +++ b/src/transport/util.rs @@ -58,7 +58,7 @@ impl Stream for FramedBincodeRead { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - self.project().0.poll_next_unpin(cx) + Pin::new(&mut self.project().0).poll_next(cx) } } @@ -110,25 +110,25 @@ impl Sink for FramedBincodeWrite { self: Pin<&mut Self>, cx: &mut task::Context<'_>, ) -> Poll> { - self.project().0.poll_ready_unpin(cx) + Pin::new(&mut self.project().0).poll_ready(cx) } fn start_send(self: Pin<&mut Self>, item: Out) -> Result<(), Self::Error> { - self.project().0.start_send_unpin(item) + Pin::new(&mut self.project().0).start_send(item) } fn poll_flush( self: Pin<&mut Self>, cx: &mut task::Context<'_>, ) -> Poll> { - self.project().0.poll_flush_unpin(cx) + Pin::new(&mut self.project().0).poll_flush(cx) } fn poll_close( self: Pin<&mut Self>, cx: &mut task::Context<'_>, ) -> Poll> { - self.project().0.poll_close_unpin(cx) + Pin::new(&mut self.project().0).poll_close(cx) } } diff --git a/tests/math.rs b/tests/math.rs index 5cc6929..c15ade1 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -6,7 +6,9 @@ #![allow(dead_code)] use async_stream::stream; use derive_more::{From, TryInto}; -use futures::{SinkExt, Stream, StreamExt, TryStreamExt}; +use futures_buffered::BufferedStreamExt; +use futures_lite::{Stream, StreamExt}; +use futures_util::SinkExt; use quic_rpc::{ message::{ BidiStreaming, BidiStreamingMsg, ClientStreaming, ClientStreamingMsg, Msg, RpcMsg, @@ -254,8 +256,8 @@ impl ComputeService { } }); process_stream - .buffer_unordered(parallelism) - .for_each(|x| async move { + .buffered_unordered(parallelism) + .for_each(|x| { if let Err(e) = x { eprintln!("error: {e:?}"); } @@ -289,7 +291,7 @@ pub async fn smoke_test>(client: C) -> anyh // server streaming call tracing::debug!("calling server_streaming Fibonacci(10)"); let s = client.server_streaming(Fibonacci(10)).await?; - let res = s.map_ok(|x| x.0).try_collect::>().await?; + let res: Vec<_> = s.map(|x| x.map(|x| x.0)).try_collect().await?; tracing::debug!("got response {:?}", res); assert_eq!(res, vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34]); @@ -302,7 +304,7 @@ pub async fn smoke_test>(client: C) -> anyh } Ok::<_, C::SendError>(()) }); - let res = recv.map_ok(|x| x.0).try_collect::>().await?; + let res: Vec<_> = recv.map(|x| x.map(|x| x.0)).try_collect().await?; tracing::debug!("got response {:?}", res); assert_eq!(res, vec![2, 4, 6]); @@ -339,8 +341,8 @@ where // parallel RPCs { let t0 = std::time::Instant::now(); - let reqs = futures::stream::iter((0..n).map(Sqr)); - let resp = reqs + let reqs = futures_lite::stream::iter((0..n).map(Sqr)); + let resp: Vec<_> = reqs .map(|x| { let client = client.clone(); async move { @@ -348,8 +350,8 @@ where anyhow::Ok(res) } }) - .buffer_unordered(32) - .try_collect::>() + .buffered_unordered(32) + .try_collect() .await?; let sum = resp.into_iter().sum::(); let rps = ((n as f64) / t0.elapsed().as_secs_f64()).round(); @@ -362,8 +364,8 @@ where let t0 = std::time::Instant::now(); let (send, recv) = client.bidi(Multiply(2)).await?; let handle = tokio::task::spawn(async move { - let requests = futures::stream::iter((0..n).map(MultiplyUpdate)); - requests.map(Ok).forward(send).await?; + let requests = futures_lite::stream::iter((0..n).map(MultiplyUpdate)); + futures_util::StreamExt::forward(requests.map(Ok), send).await?; anyhow::Result::<()>::Ok(()) }); let mut sum = 0; diff --git a/tests/slow_math.rs b/tests/slow_math.rs index e68ec10..cd4da46 100644 --- a/tests/slow_math.rs +++ b/tests/slow_math.rs @@ -7,7 +7,7 @@ mod math; use std::result; use async_stream::stream; -use futures::{Stream, StreamExt}; +use futures_lite::{Stream, StreamExt}; use math::*; use quic_rpc::{ message::{ diff --git a/tests/try.rs b/tests/try.rs index be20774..fd49a26 100644 --- a/tests/try.rs +++ b/tests/try.rs @@ -1,6 +1,6 @@ #![cfg(feature = "flume-transport")] use derive_more::{From, TryInto}; -use futures::{Stream, StreamExt}; +use futures_lite::{Stream, StreamExt}; use quic_rpc::{ message::Msg, pattern::try_server_streaming::{StreamCreated, TryServerStreaming, TryServerStreamingMsg}, From 2363cd408b17f61db83667850b306e15f0ff9f32 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Mon, 22 Apr 2024 10:45:54 +0200 Subject: [PATCH 39/49] fixup --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c7e8cb8..8f3c9f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,7 @@ quinn-transport = ["flume", "quinn", "bincode", "tokio-serde", "tokio-util"] flume-transport = ["flume"] combined-transport = [] macros = [] -default = ["flume-transport", "quinn-transport", "flume-transport", "hyper-transport", "macros", "combined-transport"] +default = ["flume-transport"] [package.metadata.docs.rs] all-features = true From f5a6b74f68a89d2c6e24c08f01d6b104ef4a83e9 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Wed, 24 Apr 2024 17:49:47 +0300 Subject: [PATCH 40/49] increase version number --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be5e76d..b159824 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -743,7 +743,7 @@ dependencies = [ [[package]] name = "quic-rpc" -version = "0.7.0" +version = "0.8.0" dependencies = [ "anyhow", "async-stream", diff --git a/Cargo.toml b/Cargo.toml index 8f3c9f0..61468d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quic-rpc" -version = "0.7.0" +version = "0.8.0" edition = "2021" authors = ["Rüdiger Klaehn "] keywords = ["api", "protocol", "network", "rpc"] From ca44b8dedc4f625346a316e623b1f2cda0f53a1b Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Wed, 24 Apr 2024 18:10:51 +0300 Subject: [PATCH 41/49] add missing tokio feature --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 61468d2..d26a953 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ hyper = { version = "0.14.16", features = ["full"], optional = true } pin-project = "1" quinn = { version = "0.10", optional = true } serde = { version = "1.0.183", features = ["derive"] } -tokio = { version = "1", default-features = false, features = ["macros"] } +tokio = { version = "1", default-features = false, features = ["macros", "sync"] } tokio-serde = { version = "0.8", features = ["bincode"], optional = true } tokio-util = { version = "0.7", features = ["codec"], optional = true } tracing = "0.1" From df5438284ec30b5b5b527932b39a48659e4518e5 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Mon, 6 May 2024 11:06:03 +0200 Subject: [PATCH 42/49] deps: Depend on iroh-quinn and bump version (#75) --- Cargo.lock | 104 ++++++++++++++-------------- Cargo.toml | 6 +- examples/split/client/Cargo.toml | 2 +- examples/split/server/Cargo.toml | 2 +- src/pattern/bidi_streaming.rs | 2 +- src/pattern/client_streaming.rs | 3 +- src/pattern/rpc.rs | 2 +- src/pattern/server_streaming.rs | 2 +- src/pattern/try_server_streaming.rs | 5 +- 9 files changed, 65 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b159824..e10d22c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,8 +132,8 @@ version = "0.1.0" dependencies = [ "anyhow", "futures", + "iroh-quinn", "quic-rpc", - "quinn", "rustls", "serde", "tokio", @@ -495,6 +495,54 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "iroh-quinn" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f3a9d551023df808c2407d7e5db069e8a994b53c9dcd35b858b4e4c220a2c4" +dependencies = [ + "bytes", + "iroh-quinn-proto", + "iroh-quinn-udp", + "pin-project-lite", + "rustc-hash", + "rustls", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "iroh-quinn-proto" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f2656b322c7f6cf3eb95e632d1c0f2fa546841915b0270da581f918c70c4be" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-native-certs", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "iroh-quinn-udp" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6679979a7271c24f9dae9622c0b4a543881508aa3a7396f55dfbaaa56f01c063" +dependencies = [ + "bytes", + "libc", + "socket2 0.5.3", + "tracing", + "windows-sys", +] + [[package]] name = "itoa" version = "1.0.9" @@ -743,7 +791,7 @@ dependencies = [ [[package]] name = "quic-rpc" -version = "0.8.0" +version = "0.9.0" dependencies = [ "anyhow", "async-stream", @@ -757,9 +805,9 @@ dependencies = [ "futures-sink", "futures-util", "hyper", + "iroh-quinn", "pin-project", "proc-macro2", - "quinn", "rcgen", "rustls", "serde", @@ -772,54 +820,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "quinn" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" -dependencies = [ - "bytes", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "thiserror", - "tokio", - "tracing", -] - -[[package]] -name = "quinn-proto" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c8bb234e70c863204303507d841e7fa2295e95c822b2bb4ca8ebf57f17b1cb" -dependencies = [ - "bytes", - "rand", - "ring", - "rustc-hash", - "rustls", - "rustls-native-certs", - "slab", - "thiserror", - "tinyvec", - "tracing", -] - -[[package]] -name = "quinn-udp" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df19e284d93757a9fb91d63672f7741b129246a669db09d1c0063071debc0c0" -dependencies = [ - "bytes", - "libc", - "socket2 0.5.3", - "tracing", - "windows-sys", -] - [[package]] name = "quote" version = "1.0.32" @@ -1038,8 +1038,8 @@ dependencies = [ "anyhow", "async-stream", "futures", + "iroh-quinn", "quic-rpc", - "quinn", "rcgen", "rustls", "serde", diff --git a/Cargo.toml b/Cargo.toml index d26a953..70a6ef8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quic-rpc" -version = "0.8.0" +version = "0.9.0" edition = "2021" authors = ["Rüdiger Klaehn "] keywords = ["api", "protocol", "network", "rpc"] @@ -23,7 +23,7 @@ futures-sink = "0.3.30" futures-util = { version = "0.3.30", features = ["sink"] } hyper = { version = "0.14.16", features = ["full"], optional = true } pin-project = "1" -quinn = { version = "0.10", optional = true } +quinn = { package = "iroh-quinn", version = "0.10", optional = true } serde = { version = "1.0.183", features = ["derive"] } tokio = { version = "1", default-features = false, features = ["macros", "sync"] } tokio-serde = { version = "0.8", features = ["bincode"], optional = true } @@ -42,7 +42,7 @@ async-stream = "0.3.3" derive_more = { version = "1.0.0-beta.1", features = ["full"] } serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } -quinn = "0.10" +quinn = { package = "iroh-quinn", version = "0.10" } rcgen = "0.10.0" rustls = "0.21" thousands = "0.2.0" diff --git a/examples/split/client/Cargo.toml b/examples/split/client/Cargo.toml index edcd431..244454d 100644 --- a/examples/split/client/Cargo.toml +++ b/examples/split/client/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" anyhow = "1.0.14" futures = "0.3.26" quic-rpc = { path = "../../..", features = ["quinn-transport", "macros"] } -quinn = "0.10" +quinn = { package = "iroh-quinn", version = "0.10" } rustls = { version = "0.21", features = ["dangerous_configuration"] } tracing-subscriber = "0.3.16" serde = { version = "1", features = ["derive"] } diff --git a/examples/split/server/Cargo.toml b/examples/split/server/Cargo.toml index 7dbdb51..71d9576 100644 --- a/examples/split/server/Cargo.toml +++ b/examples/split/server/Cargo.toml @@ -11,7 +11,7 @@ async-stream = "0.3.3" futures = "0.3.26" tracing-subscriber = "0.3.16" quic-rpc = { path = "../../..", features = ["quinn-transport", "macros"] } -quinn = "0.10" +quinn = { package = "iroh-quinn", version = "0.10" } rcgen = "0.10.0" rustls = "0.21" serde = { version = "1", features = ["derive"] } diff --git a/src/pattern/bidi_streaming.rs b/src/pattern/bidi_streaming.rs index 29d4c66..cb3e12f 100644 --- a/src/pattern/bidi_streaming.rs +++ b/src/pattern/bidi_streaming.rs @@ -1,4 +1,4 @@ -//! +//! Bidirectional stream interaction pattern. use futures_lite::{Stream, StreamExt}; use futures_util::{FutureExt, SinkExt}; diff --git a/src/pattern/client_streaming.rs b/src/pattern/client_streaming.rs index 82000ad..ba26687 100644 --- a/src/pattern/client_streaming.rs +++ b/src/pattern/client_streaming.rs @@ -1,4 +1,5 @@ -//! +//! Client streaming interaction pattern. + use futures_lite::{future::Boxed, Future, StreamExt}; use futures_util::{FutureExt, SinkExt, TryFutureExt}; diff --git a/src/pattern/rpc.rs b/src/pattern/rpc.rs index d9828f2..c07865e 100644 --- a/src/pattern/rpc.rs +++ b/src/pattern/rpc.rs @@ -1,4 +1,4 @@ -//! +//! RPC interaction pattern. use futures_lite::{Future, StreamExt}; use futures_util::{FutureExt, SinkExt}; diff --git a/src/pattern/server_streaming.rs b/src/pattern/server_streaming.rs index d3d76b2..8fa7204 100644 --- a/src/pattern/server_streaming.rs +++ b/src/pattern/server_streaming.rs @@ -1,4 +1,4 @@ -//! +//! Server streaming interaction pattern. use futures_lite::{Stream, StreamExt}; use futures_util::{FutureExt, SinkExt, TryFutureExt}; diff --git a/src/pattern/try_server_streaming.rs b/src/pattern/try_server_streaming.rs index 89fe82b..88e275f 100644 --- a/src/pattern/try_server_streaming.rs +++ b/src/pattern/try_server_streaming.rs @@ -1,4 +1,5 @@ -//! +//! Fallible server streaming interaction pattern. + use futures_lite::{Future, Stream, StreamExt}; use futures_util::{FutureExt, SinkExt, TryFutureExt}; use serde::{Deserialize, Serialize}; @@ -25,7 +26,7 @@ use std::{ #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct StreamCreated; -/// +/// Fallible server streaming interaction pattern. #[derive(Debug, Clone, Copy)] pub struct TryServerStreaming; From 6d710b74cf45d8a04cbbcd9d803718b9aceb8012 Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Mon, 6 May 2024 12:09:21 +0200 Subject: [PATCH 43/49] wip: easier generics --- examples/errors.rs | 6 ++-- examples/macro.rs | 2 +- examples/modularize.rs | 2 +- examples/split/client/src/main.rs | 10 +++---- examples/split/server/src/main.rs | 3 +- examples/store.rs | 10 +++++-- src/transport/flume.rs | 50 +++++++++++++++---------------- src/transport/quinn.rs | 44 +++++++++++++-------------- tests/flume.rs | 10 +++---- tests/quinn.rs | 36 ++++++++++++++-------- tests/try.rs | 2 +- 11 files changed, 93 insertions(+), 82 deletions(-) diff --git a/examples/errors.rs b/examples/errors.rs index 29f8d9e..26eb88b 100644 --- a/examples/errors.rs +++ b/examples/errors.rs @@ -55,9 +55,9 @@ impl Fs { #[tokio::main] async fn main() -> anyhow::Result<()> { let fs = Fs; - let (server, client) = quic_rpc::transport::flume::connection(1); - let client = RpcClient::::new(client); - let server = RpcServer::::new(server); + let (server, client) = quic_rpc::transport::flume::connection::(1); + let client = RpcClient::new(client); + let server = RpcServer::new(server); let handle = tokio::task::spawn(async move { for _ in 0..1 { let (req, chan) = server.accept().await?; diff --git a/examples/macro.rs b/examples/macro.rs index 41ed7a3..2188402 100644 --- a/examples/macro.rs +++ b/examples/macro.rs @@ -105,7 +105,7 @@ create_store_dispatch!(Store, dispatch_store_request); #[tokio::main] async fn main() -> anyhow::Result<()> { - let (server, client) = flume::connection::(1); + let (server, client) = flume::connection::(1); let server_handle = tokio::task::spawn(async move { let target = Store; run_server_loop(StoreService, server, target, dispatch_store_request).await diff --git a/examples/modularize.rs b/examples/modularize.rs index 05aa8f2..d55f1bf 100644 --- a/examples/modularize.rs +++ b/examples/modularize.rs @@ -19,7 +19,7 @@ use app::AppService; async fn main() -> Result<()> { // Spawn an inmemory connection. // Could use quic equally (all code in this example is generic over the transport) - let (server_conn, client_conn) = flume::connection::(1); + let (server_conn, client_conn) = flume::connection::(1); // spawn the server let handler = app::Handler::default(); diff --git a/examples/split/client/src/main.rs b/examples/split/client/src/main.rs index 16738fd..60512ba 100644 --- a/examples/split/client/src/main.rs +++ b/examples/split/client/src/main.rs @@ -1,5 +1,6 @@ use futures::sink::SinkExt; use futures::stream::StreamExt; +use quic_rpc::transport::quinn::QuinnConnection; use quic_rpc::RpcClient; use quinn::{ClientConfig, Endpoint}; use std::io; @@ -14,12 +15,9 @@ async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let server_addr: SocketAddr = "127.0.0.1:12345".parse()?; let endpoint = make_insecure_client_endpoint("0.0.0.0:0".parse()?)?; - let client = quic_rpc::transport::quinn::QuinnConnection::new( - endpoint, - server_addr, - "localhost".to_string(), - ); - let client = RpcClient::::new(client); + let client = + QuinnConnection::::new(endpoint, server_addr, "localhost".to_string()); + let client = RpcClient::new(client); // let mut client = ComputeClient(client); // a rpc call diff --git a/examples/split/server/src/main.rs b/examples/split/server/src/main.rs index e064395..90e1823 100644 --- a/examples/split/server/src/main.rs +++ b/examples/split/server/src/main.rs @@ -1,6 +1,7 @@ use async_stream::stream; use futures::stream::{Stream, StreamExt}; use quic_rpc::server::run_server_loop; +use quic_rpc::transport::quinn::QuinnServerEndpoint; use quinn::{Endpoint, ServerConfig}; use std::net::SocketAddr; use std::sync::Arc; @@ -61,7 +62,7 @@ async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let server_addr: SocketAddr = "127.0.0.1:12345".parse()?; let (server, _server_certs) = make_server_endpoint(server_addr)?; - let channel = quic_rpc::transport::quinn::QuinnServerEndpoint::new(server)?; + let channel = QuinnServerEndpoint::::new(server)?; let target = Compute; run_server_loop( ComputeService, diff --git a/examples/store.rs b/examples/store.rs index 1042a6b..c5cff9b 100644 --- a/examples/store.rs +++ b/examples/store.rs @@ -184,7 +184,7 @@ async fn main() -> anyhow::Result<()> { } } - let (server, client) = flume::connection::(1); + let (server, client) = flume::connection::(1); let client = RpcClient::::new(client); let server = RpcServer::::new(server); let server_handle = tokio::task::spawn(server_future(server)); @@ -231,7 +231,13 @@ async fn main() -> anyhow::Result<()> { } async fn _main_unsugared() -> anyhow::Result<()> { - let (server, client) = flume::connection::(1); + #[derive(Clone, Debug)] + struct Service; + impl crate::Service for Service { + type Req = u64; + type Res = String; + } + let (server, client) = flume::connection::(1); let to_string_service = tokio::spawn(async move { let (mut send, mut recv) = server.accept_bi().await?; while let Some(item) = recv.next().await { diff --git a/src/transport/flume.rs b/src/transport/flume.rs index f77159e..239db86 100644 --- a/src/transport/flume.rs +++ b/src/transport/flume.rs @@ -6,7 +6,7 @@ use futures_sink::Sink; use crate::{ transport::{Connection, ConnectionErrors, LocalAddr, ServerEndpoint}, - RpcMessage, + RpcMessage, Service, }; use core::fmt; use std::{error, fmt::Display, marker::PhantomData, pin::Pin, result, task::Poll}; @@ -100,11 +100,11 @@ impl error::Error for RecvError {} /// A flume based server endpoint. /// /// Created using [connection]. -pub struct FlumeServerEndpoint { - stream: flume::Receiver<(SendSink, RecvStream)>, +pub struct FlumeServerEndpoint { + stream: flume::Receiver<(SendSink, RecvStream)>, } -impl Clone for FlumeServerEndpoint { +impl Clone for FlumeServerEndpoint { fn clone(&self) -> Self { Self { stream: self.stream.clone(), @@ -112,7 +112,7 @@ impl Clone for FlumeServerEndpoint { } } -impl fmt::Debug for FlumeServerEndpoint { +impl fmt::Debug for FlumeServerEndpoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FlumeServerEndpoint") .field("stream", &self.stream) @@ -120,7 +120,7 @@ impl fmt::Debug for FlumeServerEndpoint ConnectionErrors for FlumeServerEndpoint { +impl ConnectionErrors for FlumeServerEndpoint { type SendError = self::SendError; type RecvError = self::RecvError; @@ -194,13 +194,13 @@ impl Future for AcceptBiFuture { } } -impl ConnectionCommon for FlumeServerEndpoint { - type SendSink = SendSink; - type RecvStream = RecvStream; +impl ConnectionCommon for FlumeServerEndpoint { + type SendSink = SendSink; + type RecvStream = RecvStream; } -impl ServerEndpoint for FlumeServerEndpoint { - type AcceptBiFut = AcceptBiFuture; +impl ServerEndpoint for FlumeServerEndpoint { + type AcceptBiFut = AcceptBiFuture; fn accept_bi(&self) -> Self::AcceptBiFut { AcceptBiFuture { @@ -214,7 +214,7 @@ impl ServerEndpoint for FlumeServerEnd } } -impl ConnectionErrors for FlumeConnection { +impl ConnectionErrors for FlumeConnection { type SendError = self::SendError; type RecvError = self::RecvError; @@ -222,17 +222,17 @@ impl ConnectionErrors for FlumeConnection ConnectionCommon for FlumeConnection { - type SendSink = SendSink; - type RecvStream = RecvStream; +impl ConnectionCommon for FlumeConnection { + type SendSink = SendSink; + type RecvStream = RecvStream; } -impl Connection for FlumeConnection { - type OpenBiFut = OpenBiFuture; +impl Connection for FlumeConnection { + type OpenBiFut = OpenBiFuture; fn open_bi(&self) -> Self::OpenBiFut { - let (local_send, remote_recv) = flume::bounded::(128); - let (remote_send, local_recv) = flume::bounded::(128); + let (local_send, remote_recv) = flume::bounded::(128); + let (remote_send, local_recv) = flume::bounded::(128); let remote_chan = ( SendSink(remote_send.into_sink()), RecvStream(remote_recv.into_stream()), @@ -248,11 +248,11 @@ impl Connection for FlumeConnection { - sink: flume::Sender<(SendSink, RecvStream)>, +pub struct FlumeConnection { + sink: flume::Sender<(SendSink, RecvStream)>, } -impl Clone for FlumeConnection { +impl Clone for FlumeConnection { fn clone(&self) -> Self { Self { sink: self.sink.clone(), @@ -260,7 +260,7 @@ impl Clone for FlumeConnection { } } -impl fmt::Debug for FlumeConnection { +impl fmt::Debug for FlumeConnection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FlumeClientChannel") .field("sink", &self.sink) @@ -335,9 +335,7 @@ impl std::error::Error for CreateChannelError {} /// Create a flume server endpoint and a connected flume client channel. /// /// `buffer` the size of the buffer for each channel. Keep this at a low value to get backpressure -pub fn connection( - buffer: usize, -) -> (FlumeServerEndpoint, FlumeConnection) { +pub fn connection(buffer: usize) -> (FlumeServerEndpoint, FlumeConnection) { let (sink, stream) = flume::bounded(buffer); (FlumeServerEndpoint { stream }, FlumeConnection { sink }) } diff --git a/src/transport/quinn.rs b/src/transport/quinn.rs index 0accbb5..61ef40b 100644 --- a/src/transport/quinn.rs +++ b/src/transport/quinn.rs @@ -1,7 +1,7 @@ //! QUIC transport implementation based on [quinn](https://crates.io/crates/quinn) use crate::{ transport::{Connection, ConnectionErrors, LocalAddr, ServerEndpoint}, - RpcMessage, + RpcMessage, Service, }; use futures_lite::{Future, Stream, StreamExt}; use futures_sink::Sink; @@ -55,12 +55,12 @@ impl Drop for ServerEndpointInner { /// A server endpoint using a quinn connection #[derive(Debug)] -pub struct QuinnServerEndpoint { +pub struct QuinnServerEndpoint { inner: Arc, - _phantom: PhantomData<(In, Out)>, + _phantom: PhantomData, } -impl QuinnServerEndpoint { +impl QuinnServerEndpoint { /// handles RPC requests from a connection /// /// to cleanly shutdown the handler, drop the receiver side of the sender. @@ -177,7 +177,7 @@ impl QuinnServerEndpoint { } } -impl Clone for QuinnServerEndpoint { +impl Clone for QuinnServerEndpoint { fn clone(&self) -> Self { Self { inner: self.inner.clone(), @@ -186,7 +186,7 @@ impl Clone for QuinnServerEndpoint { } } -impl ConnectionErrors for QuinnServerEndpoint { +impl ConnectionErrors for QuinnServerEndpoint { type SendError = io::Error; type RecvError = io::Error; @@ -194,13 +194,13 @@ impl ConnectionErrors for QuinnServerEndpoint ConnectionCommon for QuinnServerEndpoint { - type RecvStream = self::RecvStream; - type SendSink = self::SendSink; +impl ConnectionCommon for QuinnServerEndpoint { + type SendSink = self::SendSink; + type RecvStream = self::RecvStream; } -impl ServerEndpoint for QuinnServerEndpoint { - type AcceptBiFut = AcceptBiFuture; +impl ServerEndpoint for QuinnServerEndpoint { + type AcceptBiFut = AcceptBiFuture; fn accept_bi(&self) -> Self::AcceptBiFut { AcceptBiFuture(self.inner.receiver.clone().into_recv_async(), PhantomData) @@ -247,12 +247,12 @@ impl Drop for ClientConnectionInner { } /// A connection using a quinn connection -pub struct QuinnConnection { +pub struct QuinnConnection { inner: Arc, - _phantom: PhantomData<(In, Out)>, + _phantom: PhantomData, } -impl QuinnConnection { +impl QuinnConnection { async fn single_connection_handler_inner( connection: quinn::Connection, requests: flume::Receiver>>, @@ -594,7 +594,7 @@ impl<'a, T> Stream for Receiver<'a, T> { } } -impl fmt::Debug for QuinnConnection { +impl fmt::Debug for QuinnConnection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ClientChannel") .field("inner", &self.inner) @@ -602,7 +602,7 @@ impl fmt::Debug for QuinnConnection { } } -impl Clone for QuinnConnection { +impl Clone for QuinnConnection { fn clone(&self) -> Self { Self { inner: self.inner.clone(), @@ -611,7 +611,7 @@ impl Clone for QuinnConnection { } } -impl ConnectionErrors for QuinnConnection { +impl ConnectionErrors for QuinnConnection { type SendError = io::Error; type RecvError = io::Error; @@ -619,13 +619,13 @@ impl ConnectionErrors for QuinnConnection ConnectionCommon for QuinnConnection { - type SendSink = self::SendSink; - type RecvStream = self::RecvStream; +impl ConnectionCommon for QuinnConnection { + type SendSink = self::SendSink; + type RecvStream = self::RecvStream; } -impl Connection for QuinnConnection { - type OpenBiFut = OpenBiFuture; +impl Connection for QuinnConnection { + type OpenBiFut = OpenBiFuture; fn open_bi(&self) -> Self::OpenBiFut { let (sender, receiver) = oneshot::channel(); diff --git a/tests/flume.rs b/tests/flume.rs index e2674e7..e0b52f1 100644 --- a/tests/flume.rs +++ b/tests/flume.rs @@ -10,7 +10,7 @@ use quic_rpc::{ #[tokio::test] async fn flume_channel_bench() -> anyhow::Result<()> { tracing_subscriber::fmt::try_init().ok(); - let (server, client) = flume::connection::(1); + let (server, client) = flume::connection::(1); let server = RpcServer::::new(server); let server_handle = tokio::task::spawn(ComputeService::server(server)); @@ -59,11 +59,9 @@ async fn flume_channel_mapped_bench() -> anyhow::Result<()> { type Req = InnerRequest; type Res = InnerResponse; } - let (server, client) = flume::connection::(1); + let (server, client) = flume::connection::(1); - let server = RpcServer::::new(server); - // let server: RpcServer = server.map(); - // let server: RpcServer = server.map(); + let server = RpcServer::new(server); let server_handle: tokio::task::JoinHandle>> = tokio::task::spawn(async move { let service = ComputeService; @@ -99,7 +97,7 @@ async fn flume_channel_mapped_bench() -> anyhow::Result<()> { #[tokio::test] async fn flume_channel_smoke() -> anyhow::Result<()> { tracing_subscriber::fmt::try_init().ok(); - let (server, client) = flume::connection::(1); + let (server, client) = flume::connection::(1); let server = RpcServer::::new(server); let server_handle = tokio::task::spawn(ComputeService::server(server)); diff --git a/tests/quinn.rs b/tests/quinn.rs index 67b0f72..99eeaba 100644 --- a/tests/quinn.rs +++ b/tests/quinn.rs @@ -91,8 +91,8 @@ pub fn make_endpoints(port: u16) -> anyhow::Result { fn run_server(server: quinn::Endpoint) -> JoinHandle> { tokio::task::spawn(async move { - let connection = transport::quinn::QuinnServerEndpoint::new(server)?; - let server = RpcServer::::new(connection); + let connection = transport::quinn::QuinnServerEndpoint::::new(server)?; + let server = RpcServer::new(connection); ComputeService::server(server).await?; anyhow::Ok(()) }) @@ -110,8 +110,12 @@ async fn quinn_channel_bench() -> anyhow::Result<()> { tracing::debug!("Starting server"); let server_handle = run_server(server); tracing::debug!("Starting client"); - let client = transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); - let client = RpcClient::::new(client); + let client = transport::quinn::QuinnConnection::::new( + client, + server_addr, + "localhost".into(), + ); + let client = RpcClient::new(client); tracing::debug!("Starting benchmark"); bench(client, 50000).await?; server_handle.abort(); @@ -127,8 +131,11 @@ async fn quinn_channel_smoke() -> anyhow::Result<()> { server_addr, } = make_endpoints(12346)?; let server_handle = run_server(server); - let client_connection = - transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); + let client_connection = transport::quinn::QuinnConnection::::new( + client, + server_addr, + "localhost".into(), + ); smoke_test(client_connection).await?; server_handle.abort(); Ok(()) @@ -148,17 +155,20 @@ async fn server_away_and_back() -> anyhow::Result<()> { // create the RPC client let client = make_client_endpoint("0.0.0.0:0".parse()?, &[&server_cert])?; - let client_connection = - transport::quinn::QuinnConnection::new(client, server_addr, "localhost".into()); - let client = RpcClient::::new(client_connection); + let client_connection = transport::quinn::QuinnConnection::::new( + client, + server_addr, + "localhost".into(), + ); + let client = RpcClient::new(client_connection); // send a request. No server available so it should fail client.rpc(Sqr(4)).await.unwrap_err(); // create the RPC Server let server = Endpoint::server(server_config.clone(), server_addr)?; - let connection = transport::quinn::QuinnServerEndpoint::new(server)?; - let server = RpcServer::::new(connection); + let connection = transport::quinn::QuinnServerEndpoint::::new(server)?; + let server = RpcServer::new(connection); let server_handle = tokio::task::spawn(ComputeService::server_bounded(server, 1)); // send the first request and wait for the response to ensure everything works as expected @@ -172,8 +182,8 @@ async fn server_away_and_back() -> anyhow::Result<()> { // make the server run again let server = Endpoint::server(server_config, server_addr)?; - let connection = transport::quinn::QuinnServerEndpoint::new(server)?; - let server = RpcServer::::new(connection); + let connection = transport::quinn::QuinnServerEndpoint::::new(server)?; + let server = RpcServer::new(connection); let server_handle = tokio::task::spawn(ComputeService::server_bounded(server, 5)); // server is running, this should work diff --git a/tests/try.rs b/tests/try.rs index fd49a26..3c1e59f 100644 --- a/tests/try.rs +++ b/tests/try.rs @@ -72,7 +72,7 @@ impl Handler { #[tokio::test] async fn try_server_streaming() -> anyhow::Result<()> { tracing_subscriber::fmt::try_init().ok(); - let (server, client) = flume::connection::(1); + let (server, client) = flume::connection::(1); let server = RpcServer::::new(server); let server_handle = tokio::task::spawn(async move { From a56099e8e557776d0dc41f6c45006f879fb88f05 Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Mon, 6 May 2024 14:20:23 +0200 Subject: [PATCH 44/49] refactor: fix hyper and combined transports --- Cargo.toml | 2 +- src/transport/combined.rs | 100 +++++++++++++++++++------------------- src/transport/hyper.rs | 64 +++++++++++------------- tests/hyper.rs | 20 ++++---- 4 files changed, 88 insertions(+), 98 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d26a953..f7f6a0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ proc-macro2 = "1.0.66" futures-buffered = "0.2.4" [features] -hyper-transport = ["flume", "hyper", "bincode", "bytes"] +hyper-transport = ["flume", "hyper", "bincode", "bytes", "tokio-serde", "tokio-util"] quinn-transport = ["flume", "quinn", "bincode", "tokio-serde", "tokio-util"] flume-transport = ["flume"] combined-transport = [] diff --git a/src/transport/combined.rs b/src/transport/combined.rs index 108dc36..81a50f0 100644 --- a/src/transport/combined.rs +++ b/src/transport/combined.rs @@ -1,6 +1,6 @@ //! Transport that combines two other transports use super::{Connection, ConnectionCommon, ConnectionErrors, LocalAddr, ServerEndpoint}; -use crate::RpcMessage; +use crate::{RpcMessage, Service}; use futures_lite::{future::Boxed as BoxFuture, Stream}; use futures_sink::Sink; use futures_util::{FutureExt, TryFutureExt}; @@ -15,17 +15,17 @@ use std::{ }; /// A connection that combines two other connections -pub struct CombinedConnection { +pub struct CombinedConnection { /// First connection pub a: Option, /// Second connection pub b: Option, - /// Phantom data so we can have `In` and `Out` as type parameters - _p: PhantomData<(In, Out)>, + /// Phantom data so we can have `S` as type parameters + _p: PhantomData, } -impl, B: Connection, In: RpcMessage, Out: RpcMessage> - CombinedConnection +impl, B: Connection, S: Service> + CombinedConnection { /// Create a combined connection from two other connections /// @@ -38,9 +38,7 @@ impl, B: Connection, In: RpcMessage, Out: RpcMes } } } -impl Clone - for CombinedConnection -{ +impl Clone for CombinedConnection { fn clone(&self) -> Self { Self { a: self.a.clone(), @@ -50,9 +48,7 @@ impl Clone } } -impl Debug - for CombinedConnection -{ +impl Debug for CombinedConnection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CombinedConnection") .field("a", &self.a) @@ -62,19 +58,19 @@ impl Debug } /// An endpoint that combines two other endpoints -pub struct CombinedServerEndpoint { +pub struct CombinedServerEndpoint { /// First endpoint pub a: Option, /// Second endpoint pub b: Option, /// Local addresses from all endpoints local_addr: Vec, - /// Phantom data so we can have `In` and `Out` as type parameters - _p: PhantomData<(In, Out)>, + /// Phantom data so we can have `S` as type parameters + _p: PhantomData, } -impl, B: ServerEndpoint, In: RpcMessage, Out: RpcMessage> - CombinedServerEndpoint +impl, B: ServerEndpoint, S: Service> + CombinedServerEndpoint { /// Create a combined server endpoint from two other server endpoints /// @@ -104,9 +100,7 @@ impl, B: ServerEndpoint, In: RpcMessage, Out } } -impl Clone - for CombinedServerEndpoint -{ +impl Clone for CombinedServerEndpoint { fn clone(&self) -> Self { Self { a: self.a.clone(), @@ -117,9 +111,7 @@ impl Clone } } -impl Debug - for CombinedServerEndpoint -{ +impl Debug for CombinedServerEndpoint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CombinedServerEndpoint") .field("a", &self.a) @@ -294,25 +286,25 @@ type Socket = ( self::RecvStream, ); -impl ConnectionErrors - for CombinedConnection +impl ConnectionErrors + for CombinedConnection { type SendError = self::SendError; type RecvError = self::RecvError; type OpenError = self::OpenBiError; } -impl, B: Connection, In: RpcMessage, Out: RpcMessage> - ConnectionCommon for CombinedConnection +impl, B: Connection, S: Service> + ConnectionCommon for CombinedConnection { - type RecvStream = self::RecvStream; - type SendSink = self::SendSink; + type RecvStream = self::RecvStream; + type SendSink = self::SendSink; } -impl, B: Connection, In: RpcMessage, Out: RpcMessage> - Connection for CombinedConnection +impl, B: Connection, S: Service> + Connection for CombinedConnection { - fn open_bi(&self) -> OpenBiFuture { + fn open_bi(&self) -> OpenBiFuture { let this = self.clone(); async { // try a first, then b @@ -329,34 +321,34 @@ impl, B: Connection, In: RpcMessage, Out: RpcMes .boxed() } - type OpenBiFut = OpenBiFuture; + type OpenBiFut = OpenBiFuture; } -impl ConnectionErrors - for CombinedServerEndpoint +impl ConnectionErrors + for CombinedServerEndpoint { type SendError = self::SendError; type RecvError = self::RecvError; type OpenError = self::AcceptBiError; } -impl, B: ServerEndpoint, In: RpcMessage, Out: RpcMessage> - ConnectionCommon for CombinedServerEndpoint +impl, B: ServerEndpoint, S: Service> + ConnectionCommon for CombinedServerEndpoint { - type RecvStream = self::RecvStream; - type SendSink = self::SendSink; + type RecvStream = self::RecvStream; + type SendSink = self::SendSink; } -impl, B: ServerEndpoint, In: RpcMessage, Out: RpcMessage> - ServerEndpoint for CombinedServerEndpoint +impl, B: ServerEndpoint, S: Service> + ServerEndpoint for CombinedServerEndpoint { - fn accept_bi(&self) -> AcceptBiFuture { + fn accept_bi(&self) -> AcceptBiFuture { let a_fut = if let Some(a) = &self.a { a.accept_bi() .map_ok(|(send, recv)| { ( - SendSink::::A(send), - RecvStream::::A(recv), + SendSink::::A(send), + RecvStream::::A(recv), ) }) .map_err(AcceptBiError::A) @@ -368,8 +360,8 @@ impl, B: ServerEndpoint, In: RpcMessage, Out b.accept_bi() .map_ok(|(send, recv)| { ( - SendSink::::B(send), - RecvStream::::B(recv), + SendSink::::B(send), + RecvStream::::B(recv), ) }) .map_err(AcceptBiError::B) @@ -386,7 +378,7 @@ impl, B: ServerEndpoint, In: RpcMessage, Out .boxed() } - type AcceptBiFut = AcceptBiFuture; + type AcceptBiFut = AcceptBiFuture; fn local_addr(&self) -> &[LocalAddr] { &self.local_addr @@ -403,13 +395,19 @@ mod tests { Connection, }; + #[derive(Clone, Debug)] + struct Service; + impl crate::Service for Service { + type Req = (); + type Res = (); + } + #[tokio::test] async fn open_empty_channel() { let channel = combined::CombinedConnection::< - flume::FlumeConnection<(), ()>, - flume::FlumeConnection<(), ()>, - (), - (), + flume::FlumeConnection, + flume::FlumeConnection, + Service, >::new(None, None); let res = channel.open_bi().await; assert!(matches!(res, Err(OpenBiError::NoChannel))); diff --git a/src/transport/hyper.rs b/src/transport/hyper.rs index 4d47eff..e8b0e7c 100644 --- a/src/transport/hyper.rs +++ b/src/transport/hyper.rs @@ -7,7 +7,7 @@ use std::{ }; use crate::transport::{Connection, ConnectionErrors, LocalAddr, ServerEndpoint}; -use crate::RpcMessage; +use crate::{RpcMessage, Service}; use bytes::Bytes; use flume::{r#async::RecvFut, Receiver, Sender}; use futures_lite::{Stream, StreamExt}; @@ -33,9 +33,10 @@ struct HyperConnectionInner { } /// Hyper based connection to a server -pub struct HyperConnection { +#[derive(Clone)] +pub struct HyperConnection { inner: Arc, - _p: PhantomData<(In, Out)>, + _p: PhantomData, } /// Trait so we don't have to drag around the hyper internals @@ -49,7 +50,7 @@ impl Requester for Client { } } -impl HyperConnection { +impl HyperConnection { /// create a client given an uri and the default configuration pub fn new(uri: Uri) -> Self { Self::with_config(uri, ChannelConfig::default()) @@ -86,7 +87,7 @@ impl HyperConnection { } } -impl fmt::Debug for HyperConnection { +impl fmt::Debug for HyperConnection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ClientChannel") .field("uri", &self.inner.uri) @@ -95,15 +96,6 @@ impl fmt::Debug for HyperConnection { } } -impl Clone for HyperConnection { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - _p: PhantomData, - } - } -} - /// A pair of channels to send and receive messages on a single stream. /// /// A socket here is an abstraction of a single stream to a single peer which sends and @@ -181,9 +173,9 @@ impl Default for ChannelConfig { /// Creating this spawns a tokio task which runs the server, once dropped this task is shut /// down: no new connections will be accepted and existing channels will stop. #[derive(Debug)] -pub struct HyperServerEndpoint { +pub struct HyperServerEndpoint { /// The channel. - channel: Receiver>, + channel: Receiver>, /// The configuration. config: Arc, /// The sender to stop the server. @@ -196,11 +188,11 @@ pub struct HyperServerEndpoint { /// This is useful when the listen address uses a random port, `:0`, to find out which /// port was bound by the kernel. local_addr: [LocalAddr; 1], - /// Phantom data for in and out - _p: PhantomData<(In, Out)>, + /// Phantom data for service + _p: PhantomData, } -impl HyperServerEndpoint { +impl HyperServerEndpoint { /// Creates a server listening on the [`SocketAddr`], with the default configuration. pub fn serve(addr: &SocketAddr) -> hyper::Result { Self::serve_with_config(addr, Default::default()) @@ -260,9 +252,9 @@ impl HyperServerEndpoint { /// response and sends them to the [`ServerChannel`]. async fn handle_one_http2_request( req: Request, - accept_tx: Sender>, + accept_tx: Sender>, ) -> Result, String> { - let (req_tx, req_rx) = flume::bounded::>(32); + let (req_tx, req_rx) = flume::bounded::>(32); let (res_tx, res_rx) = flume::bounded::>(32); accept_tx .send_async((req_rx, res_tx)) @@ -373,7 +365,7 @@ fn spawn_recv_forwarder( // This does not want or need RpcMessage to be clone but still want to clone the // ServerChannel and it's containing channels itself. The derive macro can't cope with this // so this needs to be written by hand. -impl Clone for HyperServerEndpoint { +impl Clone for HyperServerEndpoint { fn clone(&self) -> Self { Self { channel: self.channel.clone(), @@ -740,8 +732,8 @@ where } } -impl HyperConnection { - fn open_bi(&self) -> OpenBiFuture { +impl HyperConnection { + fn open_bi(&self) -> OpenBiFuture { event!(Level::TRACE, "open_bi {}", self.inner.uri); let (out_tx, out_rx) = flume::bounded::>(32); let req: Result, OpenBiError> = Request::post(&self.inner.uri) @@ -758,7 +750,7 @@ impl HyperConnection { } } -impl ConnectionErrors for HyperConnection { +impl ConnectionErrors for HyperConnection { type SendError = self::SendError; type RecvError = self::RecvError; @@ -766,21 +758,21 @@ impl ConnectionErrors for HyperConnection ConnectionCommon for HyperConnection { - type RecvStream = self::RecvStream; +impl ConnectionCommon for HyperConnection { + type RecvStream = self::RecvStream; - type SendSink = self::SendSink; + type SendSink = self::SendSink; } -impl Connection for HyperConnection { - type OpenBiFut = OpenBiFuture; +impl Connection for HyperConnection { + type OpenBiFut = OpenBiFuture; fn open_bi(&self) -> Self::OpenBiFut { self.open_bi() } } -impl ConnectionErrors for HyperServerEndpoint { +impl ConnectionErrors for HyperServerEndpoint { type SendError = self::SendError; type RecvError = self::RecvError; @@ -788,13 +780,13 @@ impl ConnectionErrors for HyperServerEndpoint ConnectionCommon for HyperServerEndpoint { - type RecvStream = self::RecvStream; - type SendSink = self::SendSink; +impl ConnectionCommon for HyperServerEndpoint { + type RecvStream = self::RecvStream; + type SendSink = self::SendSink; } -impl ServerEndpoint for HyperServerEndpoint { - type AcceptBiFut = AcceptBiFuture; +impl ServerEndpoint for HyperServerEndpoint { + type AcceptBiFut = AcceptBiFuture; fn local_addr(&self) -> &[LocalAddr] { &self.local_addr diff --git a/tests/hyper.rs b/tests/hyper.rs index 0f242d3..3ccd00d 100644 --- a/tests/hyper.rs +++ b/tests/hyper.rs @@ -18,8 +18,8 @@ use math::*; mod util; fn run_server(addr: &SocketAddr) -> JoinHandle> { - let channel = HyperServerEndpoint::::serve(addr).unwrap(); - let server = RpcServer::::new(channel); + let channel = HyperServerEndpoint::::serve(addr).unwrap(); + let server = RpcServer::new(channel); tokio::spawn(async move { loop { let server = server.clone(); @@ -38,7 +38,7 @@ enum TestResponse { NoDeser(NoDeser), } -type SC = HyperServerEndpoint; +type SC = HyperServerEndpoint; /// request that can be too big #[derive(Debug, Serialize, Deserialize)] @@ -134,8 +134,8 @@ async fn hyper_channel_bench() -> anyhow::Result<()> { let addr: SocketAddr = "127.0.0.1:3000".parse()?; let uri: Uri = "http://127.0.0.1:3000".parse()?; let server_handle = run_server(&addr); - let client = HyperConnection::new(uri); - let client = RpcClient::::new(client); + let client = HyperConnection::::new(uri); + let client = RpcClient::new(client); bench(client, 50000).await?; println!("terminating server"); server_handle.abort(); @@ -148,7 +148,7 @@ async fn hyper_channel_smoke() -> anyhow::Result<()> { let addr: SocketAddr = "127.0.0.1:3001".parse()?; let uri: Uri = "http://127.0.0.1:3001".parse()?; let server_handle = run_server(&addr); - let client = HyperConnection::new(uri); + let client = HyperConnection::::new(uri); smoke_test(client).await?; server_handle.abort(); let _ = server_handle.await; @@ -171,8 +171,8 @@ async fn hyper_channel_errors() -> anyhow::Result<()> { JoinHandle>, Receiver>>, ) { - let channel = HyperServerEndpoint::serve(addr).unwrap(); - let server = RpcServer::::new(channel); + let channel = HyperServerEndpoint::::serve(addr).unwrap(); + let server = RpcServer::new(channel); let (res_tx, res_rx) = flume::unbounded(); let handle = tokio::spawn(async move { loop { @@ -212,8 +212,8 @@ async fn hyper_channel_errors() -> anyhow::Result<()> { let addr: SocketAddr = "127.0.0.1:3002".parse()?; let uri: Uri = "http://127.0.0.1:3002".parse()?; let (server_handle, server_results) = run_test_server(&addr); - let client = HyperConnection::new(uri); - let client = RpcClient::::new(client); + let client = HyperConnection::::new(uri); + let client = RpcClient::new(client); macro_rules! assert_matches { ($e:expr, $p:pat) => { From 38601de6e19e52723b30a200d1c22621c1d772f2 Mon Sep 17 00:00:00 2001 From: "Franz Heinzmann (Frando)" Date: Mon, 6 May 2024 15:04:15 +0200 Subject: [PATCH 45/49] chore: clippy --- src/transport/flume.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/transport/flume.rs b/src/transport/flume.rs index 239db86..154ec77 100644 --- a/src/transport/flume.rs +++ b/src/transport/flume.rs @@ -101,6 +101,7 @@ impl error::Error for RecvError {} /// /// Created using [connection]. pub struct FlumeServerEndpoint { + #[allow(clippy::type_complexity)] stream: flume::Receiver<(SendSink, RecvStream)>, } @@ -249,6 +250,7 @@ impl Connection for FlumeConnection { /// /// Created using [connection]. pub struct FlumeConnection { + #[allow(clippy::type_complexity)] sink: flume::Sender<(SendSink, RecvStream)>, } From 78a32506214cfa564c06fac4f468952c22734c0c Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 13 May 2024 11:10:15 +0300 Subject: [PATCH 46/49] chore: Update derive-more maybe it gets rid of the errors --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- examples/split/types/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b159824..8edce79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -791,9 +791,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c8bb234e70c863204303507d841e7fa2295e95c822b2bb4ca8ebf57f17b1cb" +checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" dependencies = [ "bytes", "rand", @@ -809,9 +809,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df19e284d93757a9fb91d63672f7741b129246a669db09d1c0063071debc0c0" +checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ "bytes", "libc", diff --git a/Cargo.toml b/Cargo.toml index f7f6a0e..f95b993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ version = "0.4.20" [dev-dependencies] anyhow = "1.0.73" async-stream = "0.3.3" -derive_more = { version = "1.0.0-beta.1", features = ["full"] } +derive_more = { version = "1.0.0-beta.6", features = ["full"] } serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } quinn = "0.10" diff --git a/examples/split/types/Cargo.toml b/examples/split/types/Cargo.toml index 8c77aa5..aea881a 100644 --- a/examples/split/types/Cargo.toml +++ b/examples/split/types/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -derive_more = { version = "1.0.0-beta.1", features = ["from", "display", "try_into"] } +derive_more = { version = "1.0.0-beta.6", features = ["from", "display", "try_into"] } futures = "0.3.26" quic-rpc = { path = "../../..", features = ["macros"] } serde = { version = "1", features = ["derive"] } From 9235fdfe0efc4cbcd9694c248d1f112f32000666 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 13 May 2024 12:01:09 +0300 Subject: [PATCH 47/49] fix: downgrade derive_more to non beta to get rid of some weird warnings about non_local_definitions from derive_more macros --- Cargo.lock | 51 +++++++++++++------------------ Cargo.toml | 3 +- examples/split/client/src/main.rs | 1 + examples/split/types/Cargo.toml | 2 +- src/lib.rs | 2 +- 5 files changed, 26 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e10d22c..0295eaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,12 +143,9 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.6.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "core-foundation" @@ -174,24 +171,15 @@ checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" [[package]] name = "derive_more" -version = "1.0.0-beta.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7abbfc297053be59290e3152f8cbcd52c8642e0728b69ee187d991d4c1af08d" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0-beta.6" +version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.28", - "unicode-xid", + "rustc_version", + "syn 1.0.109", ] [[package]] @@ -907,6 +895,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.8" @@ -1011,6 +1008,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" version = "1.0.183" @@ -1365,18 +1368,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "untrusted" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index 1655444..58b5956 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ rust-version = "1.65" [dependencies] bincode = { version = "1.3.3", optional = true } bytes = { version = "1", optional = true } +derive_more = "0.99.17" flume = { version = "0.11", optional = true } futures-lite = "2.3.0" futures-sink = "0.3.30" @@ -39,7 +40,7 @@ version = "0.4.20" [dev-dependencies] anyhow = "1.0.73" async-stream = "0.3.3" -derive_more = { version = "1.0.0-beta.6", features = ["full"] } + serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } quinn = { package = "iroh-quinn", version = "0.10" } diff --git a/examples/split/client/src/main.rs b/examples/split/client/src/main.rs index 60512ba..fc4df83 100644 --- a/examples/split/client/src/main.rs +++ b/examples/split/client/src/main.rs @@ -1,3 +1,4 @@ +#![allow(unknown_lints, non_local_definitions)] use futures::sink::SinkExt; use futures::stream::StreamExt; use quic_rpc::transport::quinn::QuinnConnection; diff --git a/examples/split/types/Cargo.toml b/examples/split/types/Cargo.toml index aea881a..ef680c0 100644 --- a/examples/split/types/Cargo.toml +++ b/examples/split/types/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -derive_more = { version = "1.0.0-beta.6", features = ["from", "display", "try_into"] } futures = "0.3.26" quic-rpc = { path = "../../..", features = ["macros"] } serde = { version = "1", features = ["derive"] } +derive_more = "0.99.17" diff --git a/src/lib.rs b/src/lib.rs index bc92ea3..115feb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,7 @@ //! } //! //! // create a transport channel, here a memory channel for testing -//! let (server, client) = quic_rpc::transport::flume::connection::(1); +//! let (server, client) = quic_rpc::transport::flume::connection::(1); //! //! // client side //! // create the rpc client given the channel and the service type From 49bf6ecca6d598ec5011ee9992b8456d98cfc2f9 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 13 May 2024 13:22:26 +0300 Subject: [PATCH 48/49] Increase rust version for CI --- .github/workflows/ci.yml | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0c31a3..1fbdb31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ concurrency: cancel-in-progress: true env: - MSRV: "1.63" + MSRV: "1.76" RUST_BACKTRACE: 1 RUSTFLAGS: -Dwarnings diff --git a/Cargo.toml b/Cargo.toml index 58b5956..ff06fb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/n0-computer/quic-rpc" description = "A streaming rpc system based on quic" # Sadly this also needs to be updated in .github/workflows/ci.yml -rust-version = "1.65" +rust-version = "1.76" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 5bfae3c9852111ae29f539a8e03aa12a7de49b65 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 13 May 2024 12:01:56 +0000 Subject: [PATCH 49/49] fix clippy --- tests/flume.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/flume.rs b/tests/flume.rs index e0b52f1..219a229 100644 --- a/tests/flume.rs +++ b/tests/flume.rs @@ -1,4 +1,5 @@ #![cfg(feature = "flume-transport")] +#![allow(non_local_definitions)] mod math; use math::*; use quic_rpc::{