diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index eef0e83ac..6db651b08 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,13 +56,6 @@ test-linux-stable: <<: *only <<: *test_and_build -test-windows-stable: - stage: test - <<: *test_and_build - <<: *only - tags: - - rust-windows - test-mac-stable: stage: test <<: *test_and_build diff --git a/README.md b/README.md index 5496985d1..45a018116 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,7 @@ Transport-agnostic `core` and transport servers for `http`, `ipc`, `websockets` **New!** Support for [clients](#Client-support). -[![Build Status][travis-image]][travis-url] -[![Build Status][appveyor-image]][appveyor-url] - -[travis-image]: https://travis-ci.org/paritytech/jsonrpc.svg?branch=master -[travis-url]: https://travis-ci.org/paritytech/jsonrpc -[appveyor-image]: https://ci.appveyor.com/api/projects/status/github/paritytech/jsonrpc?svg=true -[appveyor-url]: https://ci.appveyor.com/project/paritytech/jsonrpc/branch/master - -[Documentation](http://paritytech.github.io/jsonrpc/) +[Documentation](https://docs.rs/jsonrpc-core/) ## Sub-projects - [jsonrpc-core](./core) [![crates.io][core-image]][core-url] @@ -58,12 +50,12 @@ Transport-agnostic `core` and transport servers for `http`, `ipc`, `websockets` ```rust use jsonrpc_http_server::jsonrpc_core::{IoHandler, Value, Params}; -use jsonrpc_http_server::{ServerBuilder}; +use jsonrpc_http_server::ServerBuilder; fn main() { - let mut io = IoHandler::new(); - io.add_method("say_hello", |_params: Params| { - Ok(Value::String("hello".to_string())) + let mut io = IoHandler::default(); + io.add_method("say_hello", |_params: Params| async { + Ok(Value::String("hello".to_owned())) }); let server = ServerBuilder::new(io) @@ -105,7 +97,6 @@ fn main() { ```rust use jsonrpc_core_client::transports::local; -use jsonrpc_core::futures::future::{self, Future, FutureResult}; use jsonrpc_core::{Error, IoHandler, Result}; use jsonrpc_derive::rpc; @@ -151,5 +142,4 @@ fn main() { }; fut.wait().unwrap(); } - ``` diff --git a/_automate/publish.sh b/_automate/publish.sh index 53b9a8a71..ec389b923 100755 --- a/_automate/publish.sh +++ b/_automate/publish.sh @@ -66,6 +66,8 @@ for CRATE_DIR in ${ORDER[@]}; do if [ "$CHOICE" = "s" ]; then break fi + else + break fi done diff --git a/core-client/Cargo.toml b/core-client/Cargo.toml index fccda664a..d8414b041 100644 --- a/core-client/Cargo.toml +++ b/core-client/Cargo.toml @@ -8,7 +8,7 @@ keywords = ["jsonrpc", "json-rpc", "json", "rpc", "serde"] license = "MIT" name = "jsonrpc-core-client" repository = "https://github.com/paritytech/jsonrpc" -version = "14.2.0" +version = "17.1.0" categories = [ "asynchronous", @@ -26,7 +26,8 @@ ipc = ["jsonrpc-client-transports/ipc"] arbitrary_precision = ["jsonrpc-client-transports/arbitrary_precision"] [dependencies] -jsonrpc-client-transports = { version = "14.2", path = "./transports", default-features = false } +jsonrpc-client-transports = { version = "17.1", path = "./transports", default-features = false } +futures = { version = "0.3", features = [ "compat" ] } [badges] travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} diff --git a/core-client/src/lib.rs b/core-client/src/lib.rs index b35f33af8..0ffd970b7 100644 --- a/core-client/src/lib.rs +++ b/core-client/src/lib.rs @@ -3,8 +3,9 @@ //! By default this crate does not implement any transports, //! use corresponding features (`tls`, `http` or `ws`) to opt-in for them. //! -//! See documentation of [`jsonrpc-client-transports`](../jsonrpc_client_transports/) for more details. +//! See documentation of [`jsonrpc-client-transports`](https://docs.rs/jsonrpc-client-transports) for more details. #![deny(missing_docs)] +pub use futures; pub use jsonrpc_client_transports::*; diff --git a/core-client/transports/Cargo.toml b/core-client/transports/Cargo.toml index 16b97f04c..af4b3a62a 100644 --- a/core-client/transports/Cargo.toml +++ b/core-client/transports/Cargo.toml @@ -8,7 +8,7 @@ keywords = ["jsonrpc", "json-rpc", "json", "rpc", "serde"] license = "MIT" name = "jsonrpc-client-transports" repository = "https://github.com/paritytech/jsonrpc" -version = "14.2.0" +version = "17.1.0" categories = [ "asynchronous", @@ -21,10 +21,11 @@ categories = [ [features] default = ["http", "tls", "ws"] tls = ["hyper-tls", "http"] -http = ["hyper"] +http = ["hyper", "tokio/full"] ws = [ "websocket", "tokio", + "futures/compat" ] ipc = [ "parity-tokio-ipc", @@ -34,28 +35,28 @@ ipc = [ arbitrary_precision = ["serde_json/arbitrary_precision", "jsonrpc-core/arbitrary_precision"] [dependencies] -failure = "0.1" -futures = "0.1.26" -hyper = { version = "0.12", optional = true } -hyper-tls = { version = "0.3.2", optional = true } -jsonrpc-core = { version = "14.2", path = "../../core" } -jsonrpc-pubsub = { version = "14.2", path = "../../pubsub" } -jsonrpc-server-utils = { version = "14.2", path = "../../server-utils", optional = true } +derive_more = "0.99" +futures = "0.3" +jsonrpc-core = { version = "17.1", path = "../../core" } +jsonrpc-pubsub = { version = "17.1", path = "../../pubsub" } log = "0.4" -parity-tokio-ipc = { version = "0.2", optional = true } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -tokio = { version = "0.1", optional = true } -websocket = { version = "0.24", optional = true } url = "1.7" +hyper = { version = "0.13", optional = true } +hyper-tls = { version = "0.4", optional = true } +jsonrpc-server-utils = { version = "17.1", path = "../../server-utils", optional = true } +parity-tokio-ipc = { version = "0.8", optional = true } +tokio = { version = "0.2", optional = true } +websocket = { version = "0.24", optional = true } + [dev-dependencies] assert_matches = "1.1" -jsonrpc-http-server = { version = "14.2", path = "../../http" } -jsonrpc-ipc-server = { version = "14.2", path = "../../ipc" } +jsonrpc-http-server = { version = "17.1", path = "../../http" } +jsonrpc-ipc-server = { version = "17.1", path = "../../ipc" } lazy_static = "1.0" env_logger = "0.7" -tokio = "0.1" [badges] travis-ci = { repository = "paritytech/jsonrpc", branch = "master" } diff --git a/core-client/transports/src/lib.rs b/core-client/transports/src/lib.rs index b6da7c7e2..001177a33 100644 --- a/core-client/transports/src/lib.rs +++ b/core-client/transports/src/lib.rs @@ -2,14 +2,18 @@ #![deny(missing_docs)] -use failure::{format_err, Fail}; -use futures::sync::{mpsc, oneshot}; -use futures::{future, prelude::*}; +use jsonrpc_core::futures::channel::{mpsc, oneshot}; +use jsonrpc_core::futures::{ + self, + task::{Context, Poll}, + Future, Stream, StreamExt, +}; use jsonrpc_core::{Error, Params}; use serde::de::DeserializeOwned; use serde::Serialize; use serde_json::Value; use std::marker::PhantomData; +use std::pin::Pin; pub mod transports; @@ -17,20 +21,34 @@ pub mod transports; mod logger; /// The errors returned by the client. -#[derive(Debug, Fail)] +#[derive(Debug, derive_more::Display)] pub enum RpcError { /// An error returned by the server. - #[fail(display = "Server returned rpc error {}", _0)] + #[display(fmt = "Server returned rpc error {}", _0)] JsonRpcError(Error), /// Failure to parse server response. - #[fail(display = "Failed to parse server response as {}: {}", _0, _1)] - ParseError(String, failure::Error), + #[display(fmt = "Failed to parse server response as {}: {}", _0, _1)] + ParseError(String, Box), /// Request timed out. - #[fail(display = "Request timed out")] + #[display(fmt = "Request timed out")] Timeout, + /// A general client error. + #[display(fmt = "Client error: {}", _0)] + Client(String), /// Not rpc specific errors. - #[fail(display = "{}", _0)] - Other(failure::Error), + #[display(fmt = "{}", _0)] + Other(Box), +} + +impl std::error::Error for RpcError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::JsonRpcError(ref e) => Some(e), + Self::ParseError(_, ref e) => Some(&**e), + Self::Other(ref e) => Some(&**e), + _ => None, + } + } } impl From for RpcError { @@ -39,6 +57,9 @@ impl From for RpcError { } } +/// A result returned by the client. +pub type RpcResult = Result; + /// An RPC call message. struct CallMessage { /// The RPC method name. @@ -47,7 +68,7 @@ struct CallMessage { params: Params, /// The oneshot channel to send the result of the rpc /// call to. - sender: oneshot::Sender>, + sender: oneshot::Sender>, } /// An RPC notification. @@ -75,7 +96,7 @@ struct SubscribeMessage { /// The subscription to subscribe to. subscription: Subscription, /// The channel to send notifications to. - sender: mpsc::Sender>, + sender: mpsc::UnboundedSender>, } /// A message sent to the `RpcClient`. @@ -108,76 +129,25 @@ impl From for RpcMessage { /// A channel to a `RpcClient`. #[derive(Clone)] -pub struct RpcChannel(mpsc::Sender); +pub struct RpcChannel(mpsc::UnboundedSender); impl RpcChannel { - fn send( - &self, - msg: RpcMessage, - ) -> impl Future, Error = mpsc::SendError> { - self.0.to_owned().send(msg) + fn send(&self, msg: RpcMessage) -> Result<(), mpsc::TrySendError> { + self.0.unbounded_send(msg) } } -impl From> for RpcChannel { - fn from(sender: mpsc::Sender) -> Self { +impl From> for RpcChannel { + fn from(sender: mpsc::UnboundedSender) -> Self { RpcChannel(sender) } } /// The future returned by the rpc call. -pub struct RpcFuture { - recv: oneshot::Receiver>, -} - -impl RpcFuture { - /// Creates a new `RpcFuture`. - pub fn new(recv: oneshot::Receiver>) -> Self { - RpcFuture { recv } - } -} - -impl Future for RpcFuture { - type Item = Value; - type Error = RpcError; - - fn poll(&mut self) -> Result, Self::Error> { - // TODO should timeout (#410) - match self.recv.poll() { - Ok(Async::Ready(Ok(value))) => Ok(Async::Ready(value)), - Ok(Async::Ready(Err(error))) => Err(error), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(error) => Err(RpcError::Other(error.into())), - } - } -} +pub type RpcFuture = oneshot::Receiver>; /// The stream returned by a subscribe. -pub struct SubscriptionStream { - recv: mpsc::Receiver>, -} - -impl SubscriptionStream { - /// Crates a new `SubscriptionStream`. - pub fn new(recv: mpsc::Receiver>) -> Self { - SubscriptionStream { recv } - } -} - -impl Stream for SubscriptionStream { - type Item = Value; - type Error = RpcError; - - fn poll(&mut self) -> Result>, Self::Error> { - match self.recv.poll() { - Ok(Async::Ready(Some(Ok(value)))) => Ok(Async::Ready(Some(value))), - Ok(Async::Ready(Some(Err(error)))) => Err(error), - Ok(Async::Ready(None)) => Ok(Async::Ready(None)), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(()) => Err(RpcError::Other(format_err!("mpsc channel returned an error."))), - } - } -} +pub type SubscriptionStream = mpsc::UnboundedReceiver>; /// A typed subscription stream. pub struct TypedSubscriptionStream { @@ -197,19 +167,20 @@ impl TypedSubscriptionStream { } } -impl Stream for TypedSubscriptionStream { - type Item = T; - type Error = RpcError; - - fn poll(&mut self) -> Result>, Self::Error> { - let result = match self.stream.poll()? { - Async::Ready(Some(value)) => serde_json::from_value::(value) - .map(|result| Async::Ready(Some(result))) - .map_err(|error| RpcError::ParseError(self.returns.into(), error.into()))?, - Async::Ready(None) => Async::Ready(None), - Async::NotReady => Async::NotReady, - }; - Ok(result) +impl Stream for TypedSubscriptionStream { + type Item = RpcResult; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let result = futures::ready!(self.stream.poll_next_unpin(cx)); + match result { + Some(Ok(value)) => Some( + serde_json::from_value::(value) + .map_err(|error| RpcError::ParseError(self.returns.into(), Box::new(error))), + ), + None => None, + Some(Err(err)) => Some(Err(err.into())), + } + .into() } } @@ -225,29 +196,31 @@ impl From for RawClient { impl RawClient { /// Call RPC method with raw JSON. - pub fn call_method(&self, method: &str, params: Params) -> impl Future { + pub fn call_method(&self, method: &str, params: Params) -> impl Future> { let (sender, receiver) = oneshot::channel(); let msg = CallMessage { method: method.into(), params, sender, }; - self.0 - .send(msg.into()) - .map_err(|error| RpcError::Other(error.into())) - .and_then(|_| RpcFuture::new(receiver)) + let result = self.0.send(msg.into()); + async move { + let () = result.map_err(|e| RpcError::Other(Box::new(e)))?; + + receiver.await.map_err(|e| RpcError::Other(Box::new(e)))? + } } /// Send RPC notification with raw JSON. - pub fn notify(&self, method: &str, params: Params) -> impl Future { + pub fn notify(&self, method: &str, params: Params) -> RpcResult<()> { let msg = NotifyMessage { method: method.into(), params, }; - self.0 - .send(msg.into()) - .map(|_| ()) - .map_err(|error| RpcError::Other(error.into())) + match self.0.send(msg.into()) { + Ok(()) => Ok(()), + Err(error) => Err(RpcError::Other(Box::new(error))), + } } /// Subscribe to topic with raw JSON. @@ -257,8 +230,8 @@ impl RawClient { subscribe_params: Params, notification: &str, unsubscribe: &str, - ) -> impl Future { - let (sender, receiver) = mpsc::channel(0); + ) -> RpcResult { + let (sender, receiver) = mpsc::unbounded(); let msg = SubscribeMessage { subscription: Subscription { subscribe: subscribe.into(), @@ -268,10 +241,11 @@ impl RawClient { }, sender, }; + self.0 .send(msg.into()) - .map_err(|error| RpcError::Other(error.into())) - .map(|_| SubscriptionStream::new(receiver)) + .map(|()| receiver) + .map_err(|e| RpcError::Other(Box::new(e))) } } @@ -292,48 +266,49 @@ impl TypedClient { } /// Call RPC with serialization of request and deserialization of response. - pub fn call_method( + pub fn call_method( &self, method: &str, - returns: &'static str, + returns: &str, args: T, - ) -> impl Future { + ) -> impl Future> { + let returns = returns.to_owned(); let args = serde_json::to_value(args).expect("Only types with infallible serialisation can be used for JSON-RPC"); let params = match args { - Value::Array(vec) => Params::Array(vec), - Value::Null => Params::None, - Value::Object(map) => Params::Map(map), - _ => { - return future::Either::A(future::err(RpcError::Other(format_err!( - "RPC params should serialize to a JSON array, JSON object or null" - )))) - } + Value::Array(vec) => Ok(Params::Array(vec)), + Value::Null => Ok(Params::None), + Value::Object(map) => Ok(Params::Map(map)), + _ => Err(RpcError::Client( + "RPC params should serialize to a JSON array, JSON object or null".into(), + )), }; + let result = params.map(|params| self.0.call_method(method, params)); + + async move { + let value: Value = result?.await?; - future::Either::B(self.0.call_method(method, params).and_then(move |value: Value| { log::debug!("response: {:?}", value); - let result = - serde_json::from_value::(value).map_err(|error| RpcError::ParseError(returns.into(), error.into())); - future::done(result) - })) + + serde_json::from_value::(value).map_err(|error| RpcError::ParseError(returns, Box::new(error))) + } } /// Call RPC with serialization of request only. - pub fn notify(&self, method: &str, args: T) -> impl Future { + pub fn notify(&self, method: &str, args: T) -> RpcResult<()> { let args = serde_json::to_value(args).expect("Only types with infallible serialisation can be used for JSON-RPC"); let params = match args { Value::Array(vec) => Params::Array(vec), Value::Null => Params::None, _ => { - return future::Either::A(future::err(RpcError::Other(format_err!( - "RPC params should serialize to a JSON array, or null" - )))) + return Err(RpcError::Client( + "RPC params should serialize to a JSON array, or null".into(), + )) } }; - future::Either::B(self.0.notify(method, params)) + self.0.notify(method, params) } /// Subscribe with serialization of request and deserialization of response. @@ -344,7 +319,7 @@ impl TypedClient { topic: &str, unsubscribe: &str, returns: &'static str, - ) -> impl Future, Error = RpcError> { + ) -> RpcResult> { let args = serde_json::to_value(subscribe_params) .expect("Only types with infallible serialisation can be used for JSON-RPC"); @@ -352,17 +327,15 @@ impl TypedClient { Value::Array(vec) => Params::Array(vec), Value::Null => Params::None, _ => { - return future::Either::A(future::err(RpcError::Other(format_err!( - "RPC params should serialize to a JSON array, or null" - )))) + return Err(RpcError::Client( + "RPC params should serialize to a JSON array, or null".into(), + )) } }; - let typed_stream = self - .0 + self.0 .subscribe(subscribe, params, topic, unsubscribe) - .map(move |stream| TypedSubscriptionStream::new(stream, returns)); - future::Either::B(typed_stream) + .map(move |stream| TypedSubscriptionStream::new(stream, returns)) } } @@ -370,7 +343,8 @@ impl TypedClient { mod tests { use super::*; use crate::transports::local; - use crate::{RpcChannel, RpcError, TypedClient}; + use crate::{RpcChannel, TypedClient}; + use jsonrpc_core::futures::{future, FutureExt}; use jsonrpc_core::{self as core, IoHandler}; use jsonrpc_pubsub::{PubSubHandler, Subscriber, SubscriptionId}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -386,11 +360,11 @@ mod tests { } impl AddClient { - fn add(&self, a: u64, b: u64) -> impl Future { + fn add(&self, a: u64, b: u64) -> impl Future> { self.0.call_method("add", "u64", (a, b)) } - fn completed(&self, success: bool) -> impl Future { + fn completed(&self, success: bool) -> RpcResult<()> { self.0.notify("completed", (success,)) } } @@ -399,65 +373,61 @@ mod tests { fn test_client_terminates() { crate::logger::init_log(); let mut handler = IoHandler::new(); - handler.add_method("add", |params: Params| { + handler.add_sync_method("add", |params: Params| { let (a, b) = params.parse::<(u64, u64)>()?; let res = a + b; Ok(jsonrpc_core::to_value(res).unwrap()) }); + let (tx, rx) = std::sync::mpsc::channel(); let (client, rpc_client) = local::connect::(handler); - let fut = client - .clone() - .add(3, 4) - .and_then(move |res| client.add(res, 5)) - .join(rpc_client) - .map(|(res, ())| { - assert_eq!(res, 12); - }) - .map_err(|err| { - eprintln!("{:?}", err); - assert!(false); - }); - tokio::run(fut); + let fut = async move { + let res = client.add(3, 4).await?; + let res = client.add(res, 5).await?; + assert_eq!(res, 12); + tx.send(()).unwrap(); + Ok(()) as RpcResult<_> + }; + let pool = futures::executor::ThreadPool::builder().pool_size(1).create().unwrap(); + pool.spawn_ok(rpc_client.map(|x| x.unwrap())); + pool.spawn_ok(fut.map(|x| x.unwrap())); + rx.recv().unwrap() } #[test] fn should_send_notification() { crate::logger::init_log(); + let (tx, rx) = std::sync::mpsc::sync_channel(1); let mut handler = IoHandler::new(); - handler.add_notification("completed", |params: Params| { + handler.add_notification("completed", move |params: Params| { let (success,) = params.parse::<(bool,)>().expect("expected to receive one boolean"); assert_eq!(success, true); + tx.send(()).unwrap(); }); let (client, rpc_client) = local::connect::(handler); - let fut = client - .clone() - .completed(true) - .map(move |()| drop(client)) - .join(rpc_client) - .map(|_| ()) - .map_err(|err| { - eprintln!("{:?}", err); - assert!(false); - }); - tokio::run(fut); + client.completed(true).unwrap(); + let pool = futures::executor::ThreadPool::builder().pool_size(1).create().unwrap(); + pool.spawn_ok(rpc_client.map(|x| x.unwrap())); + rx.recv().unwrap() } #[test] fn should_handle_subscription() { crate::logger::init_log(); // given + let (finish, finished) = std::sync::mpsc::sync_channel(1); let mut handler = PubSubHandler::::default(); let called = Arc::new(AtomicBool::new(false)); let called2 = called.clone(); handler.add_subscription( "hello", - ("subscribe_hello", |params, _meta, subscriber: Subscriber| { + ("subscribe_hello", move |params, _meta, subscriber: Subscriber| { assert_eq!(params, core::Params::None); let sink = subscriber .assign_id(SubscriptionId::Number(5)) .expect("assigned subscription id"); + let finish = finish.clone(); std::thread::spawn(move || { for i in 0..3 { std::thread::sleep(std::time::Duration::from_millis(100)); @@ -465,49 +435,45 @@ mod tests { "subscription": 5, "result": vec![i], }); - sink.notify(serde_json::from_value(value).unwrap()) - .wait() - .expect("sent notification"); + let _ = sink.notify(serde_json::from_value(value).unwrap()); } + finish.send(()).unwrap(); }); }), ("unsubscribe_hello", move |id, _meta| { // Should be called because session is dropped. called2.store(true, Ordering::SeqCst); assert_eq!(id, SubscriptionId::Number(5)); - future::ok(core::Value::Bool(true)) + future::ready(Ok(core::Value::Bool(true))) }), ); // when + let (tx, rx) = std::sync::mpsc::channel(); let (client, rpc_client) = local::connect_with_pubsub::(handler); let received = Arc::new(std::sync::Mutex::new(vec![])); let r2 = received.clone(); - let fut = client - .subscribe::<_, (u32,)>("subscribe_hello", (), "hello", "unsubscribe_hello", "u32") - .and_then(|stream| { - stream - .into_future() - .map(move |(result, _)| { - drop(client); - r2.lock().unwrap().push(result.unwrap()); - }) - .map_err(|_| { - panic!("Expected message not received."); - }) - }) - .join(rpc_client) - .map(|(res, _)| { - log::info!("ok {:?}", res); - }) - .map_err(|err| { - log::error!("err {:?}", err); - }); - tokio::run(fut); - assert_eq!(called.load(Ordering::SeqCst), true); + let fut = async move { + let mut stream = + client.subscribe::<_, (u32,)>("subscribe_hello", (), "hello", "unsubscribe_hello", "u32")?; + let result = stream.next().await; + r2.lock().unwrap().push(result.expect("Expected at least one item.")); + tx.send(()).unwrap(); + Ok(()) as RpcResult<_> + }; + + let pool = futures::executor::ThreadPool::builder().pool_size(1).create().unwrap(); + pool.spawn_ok(rpc_client.map(|_| ())); + pool.spawn_ok(fut.map(|x| x.unwrap())); + + rx.recv().unwrap(); assert!( !received.lock().unwrap().is_empty(), "Expected at least one received item." ); + // The session is being dropped only when another notification is received. + // TODO [ToDr] we should unsubscribe as soon as the stream is dropped instead! + finished.recv().unwrap(); + assert_eq!(called.load(Ordering::SeqCst), true, "Unsubscribe not called."); } } diff --git a/core-client/transports/src/transports/duplex.rs b/core-client/transports/src/transports/duplex.rs index 5465c37e4..c92cd1fe8 100644 --- a/core-client/transports/src/transports/duplex.rs +++ b/core-client/transports/src/transports/duplex.rs @@ -1,17 +1,20 @@ //! Duplex transport -use failure::format_err; -use futures::prelude::*; -use futures::sync::{mpsc, oneshot}; +use futures::channel::{mpsc, oneshot}; +use futures::{ + task::{Context, Poll}, + Future, Sink, Stream, StreamExt, +}; use jsonrpc_core::Id; use jsonrpc_pubsub::SubscriptionId; use log::debug; use serde_json::Value; use std::collections::HashMap; use std::collections::VecDeque; +use std::pin::Pin; use super::RequestBuilder; -use crate::{RpcChannel, RpcError, RpcMessage}; +use crate::{RpcChannel, RpcError, RpcMessage, RpcResult}; struct Subscription { /// Subscription id received when subscribing. @@ -21,11 +24,11 @@ struct Subscription { /// Rpc method to unsubscribe. unsubscribe: String, /// Where to send messages to. - channel: mpsc::Sender>, + channel: mpsc::UnboundedSender>, } impl Subscription { - fn new(channel: mpsc::Sender>, notification: String, unsubscribe: String) -> Self { + fn new(channel: mpsc::UnboundedSender>, notification: String, unsubscribe: String) -> Self { Subscription { id: None, notification, @@ -36,7 +39,7 @@ impl Subscription { } enum PendingRequest { - Call(oneshot::Sender>), + Call(oneshot::Sender>), Subscription(Subscription), } @@ -45,24 +48,24 @@ enum PendingRequest { pub struct Duplex { request_builder: RequestBuilder, /// Channel from the client. - channel: Option>, + channel: Option>, /// Requests that haven't received a response yet. pending_requests: HashMap, /// A map from the subscription name to the subscription. subscriptions: HashMap<(SubscriptionId, String), Subscription>, /// Incoming messages from the underlying transport. - stream: TStream, + stream: Pin>, /// Unprocessed incoming messages. - incoming: VecDeque<(Id, Result, Option, Option)>, + incoming: VecDeque<(Id, RpcResult, Option, Option)>, /// Unprocessed outgoing messages. outgoing: VecDeque, /// Outgoing messages from the underlying transport. - sink: TSink, + sink: Pin>, } impl Duplex { /// Creates a new `Duplex`. - fn new(sink: TSink, stream: TStream, channel: mpsc::Receiver) -> Self { + fn new(sink: Pin>, stream: Pin>, channel: mpsc::UnboundedReceiver) -> Self { log::debug!("open"); Duplex { request_builder: RequestBuilder::new(), @@ -78,21 +81,24 @@ impl Duplex { } /// Creates a new `Duplex`, along with a channel to communicate -pub fn duplex(sink: TSink, stream: TStream) -> (Duplex, RpcChannel) { - let (sender, receiver) = mpsc::channel(0); +pub fn duplex(sink: Pin>, stream: Pin>) -> (Duplex, RpcChannel) +where + TSink: Sink, + TStream: Stream, +{ + let (sender, receiver) = mpsc::unbounded(); let client = Duplex::new(sink, stream, receiver); (client, sender.into()) } impl Future for Duplex where - TSink: Sink, - TStream: Stream, + TSink: Sink, + TStream: Stream, { - type Item = (); - type Error = RpcError; + type Output = RpcResult<()>; - fn poll(&mut self) -> Result, Self::Error> { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { // Handle requests from the client. log::debug!("handle requests from client"); loop { @@ -101,16 +107,15 @@ where Some(channel) => channel, None => break, }; - let msg = match channel.poll() { - Ok(Async::Ready(Some(msg))) => msg, - Ok(Async::Ready(None)) => { + let msg = match channel.poll_next_unpin(cx) { + Poll::Ready(Some(msg)) => msg, + Poll::Ready(None) => { // When the channel is dropped we still need to finish // outstanding requests. self.channel.take(); break; } - Ok(Async::NotReady) => break, - Err(()) => continue, + Poll::Pending => break, }; let request_str = match msg { RpcMessage::Call(msg) => { @@ -154,17 +159,16 @@ where // Reads from stream and queues to incoming queue. log::debug!("handle stream"); loop { - let response_str = match self.stream.poll() { - Ok(Async::Ready(Some(response_str))) => response_str, - Ok(Async::Ready(None)) => { + let response_str = match self.stream.as_mut().poll_next(cx) { + Poll::Ready(Some(response_str)) => response_str, + Poll::Ready(None) => { // The websocket connection was closed so the client // can be shutdown. Reopening closed connections must // be handled by the transport. debug!("connection closed"); - return Ok(Async::Ready(())); + return Poll::Ready(Ok(())); } - Ok(Async::NotReady) => break, - Err(err) => Err(err)?, + Poll::Pending => break, }; log::debug!("incoming: {}", response_str); // we only send one request at the time, so there can only be one response. @@ -190,7 +194,7 @@ where // It's a regular Req-Res call, so just answer. Some(PendingRequest::Call(tx)) => { tx.send(result) - .map_err(|_| RpcError::Other(format_err!("oneshot channel closed")))?; + .map_err(|_| RpcError::Client("oneshot channel closed".into()))?; continue; } // It was a subscription request, @@ -214,27 +218,14 @@ where ); } } else { - let err = RpcError::Other(format_err!( + let err = RpcError::Client(format!( "Subscription {:?} ({:?}) rejected: {:?}", - id, - method, - result, + id, method, result, )); - match subscription.channel.poll_ready() { - Ok(Async::Ready(())) => { - subscription - .channel - .try_send(result) - .expect("The channel is ready; qed"); - } - Ok(Async::NotReady) => { - self.incoming.push_back((id, result, Some(method), sid)); - break; - } - Err(_) => { - log::warn!("{}, but the reply channel has closed.", err); - } - }; + + if subscription.channel.unbounded_send(result).is_err() { + log::warn!("{}, but the reply channel has closed.", err); + } } continue; } @@ -254,33 +245,21 @@ where }; if let Some(subscription) = self.subscriptions.get_mut(&sid_and_method) { - match subscription.channel.poll_ready() { - Ok(Async::Ready(())) => { - subscription - .channel - .try_send(result) - .expect("The channel is ready; qed"); - } - Ok(Async::NotReady) => { - let (sid, method) = sid_and_method; - self.incoming.push_back((id, result, Some(method), Some(sid))); - break; - } - Err(_) => { - let subscription = self - .subscriptions - .remove(&sid_and_method) - .expect("Subscription was just polled; qed"); - let sid = subscription.id.expect( - "Every subscription that ends up in `self.subscriptions` has id already \ - assigned; assignment happens during response to subscribe request.", - ); - let (_id, request_str) = - self.request_builder.unsubscribe_request(subscription.unsubscribe, sid); - log::debug!("outgoing: {}", request_str); - self.outgoing.push_back(request_str); - log::debug!("unsubscribed from {:?}", sid_and_method); - } + let res = subscription.channel.unbounded_send(result); + if res.is_err() { + let subscription = self + .subscriptions + .remove(&sid_and_method) + .expect("Subscription was just polled; qed"); + let sid = subscription.id.expect( + "Every subscription that ends up in `self.subscriptions` has id already \ + assigned; assignment happens during response to subscribe request.", + ); + let (_id, request_str) = + self.request_builder.unsubscribe_request(subscription.unsubscribe, sid); + log::debug!("outgoing: {}", request_str); + self.outgoing.push_back(request_str); + log::debug!("unsubscribed from {:?}", sid_and_method); } } else { log::warn!("Received unexpected subscription notification: {:?}", sid_and_method); @@ -294,21 +273,27 @@ where // Writes queued messages to sink. log::debug!("handle outgoing"); loop { + let err = || Err(RpcError::Client("closing".into())); + match self.sink.as_mut().poll_ready(cx) { + Poll::Ready(Ok(())) => {} + Poll::Ready(Err(_)) => return err().into(), + _ => break, + } match self.outgoing.pop_front() { - Some(request) => match self.sink.start_send(request)? { - AsyncSink::Ready => {} - AsyncSink::NotReady(request) => { - self.outgoing.push_front(request); - break; + Some(request) => { + if let Err(_) = self.sink.as_mut().start_send(request) { + // the channel is disconnected. + return err().into(); } - }, + } None => break, } } log::debug!("handle sink"); - let sink_empty = match self.sink.poll_complete()? { - Async::Ready(()) => true, - Async::NotReady => false, + let sink_empty = match self.sink.as_mut().poll_flush(cx) { + Poll::Ready(Ok(())) => true, + Poll::Ready(Err(_)) => true, + Poll::Pending => false, }; log::debug!("{:?}", self); @@ -321,9 +306,9 @@ where && sink_empty { log::debug!("close"); - Ok(Async::Ready(())) + Poll::Ready(Ok(())) } else { - Ok(Async::NotReady) + Poll::Pending } } } diff --git a/core-client/transports/src/transports/http.rs b/core-client/transports/src/transports/http.rs index ba7d3f12a..248b41890 100644 --- a/core-client/transports/src/transports/http.rs +++ b/core-client/transports/src/transports/http.rs @@ -3,58 +3,53 @@ //! HTTPS support is enabled with the `tls` feature. use super::RequestBuilder; -use crate::{RpcChannel, RpcError, RpcMessage}; -use failure::format_err; -use futures::{ - future::{ - self, - Either::{A, B}, - }, - sync::mpsc, - Future, Stream, -}; -use hyper::{http, rt, Client, Request, Uri}; +use crate::{RpcChannel, RpcError, RpcMessage, RpcResult}; +use futures::{future, Future, FutureExt, StreamExt, TryFutureExt}; +use hyper::{http, Client, Request, Uri}; /// Create a HTTP Client -pub fn connect(url: &str) -> impl Future +pub async fn connect(url: &str) -> RpcResult where TClient: From, { + let url: Uri = url.parse().map_err(|e| RpcError::Other(Box::new(e)))?; + + let (client_api, client_worker) = do_connect(url).await; + tokio::spawn(client_worker); + + Ok(TClient::from(client_api)) +} + +async fn do_connect(url: Uri) -> (RpcChannel, impl Future) { let max_parallel = 8; - let url: Uri = match url.parse() { - Ok(url) => url, - Err(e) => return A(future::err(RpcError::Other(e.into()))), - }; #[cfg(feature = "tls")] - let connector = match hyper_tls::HttpsConnector::new(4) { - Ok(connector) => connector, - Err(e) => return A(future::err(RpcError::Other(e.into()))), - }; + let connector = hyper_tls::HttpsConnector::new(); #[cfg(feature = "tls")] let client = Client::builder().build::<_, hyper::Body>(connector); #[cfg(not(feature = "tls"))] let client = Client::new(); - + // Keep track of internal request IDs when building subsequent requests let mut request_builder = RequestBuilder::new(); - let (sender, receiver) = mpsc::channel(max_parallel); + let (sender, receiver) = futures::channel::mpsc::unbounded(); let fut = receiver .filter_map(move |msg: RpcMessage| { - let (request, sender) = match msg { + future::ready(match msg { RpcMessage::Call(call) => { let (_, request) = request_builder.call_request(&call); - (request, Some(call.sender)) + Some((request, Some(call.sender))) } - RpcMessage::Notify(notify) => (request_builder.notification(¬ify), None), + RpcMessage::Notify(notify) => Some((request_builder.notification(¬ify), None)), RpcMessage::Subscribe(_) => { log::warn!("Unsupported `RpcMessage` type `Subscribe`."); - return None; + None } - }; - + }) + }) + .map(move |(request, sender)| { let request = Request::post(&url) .header( http::header::CONTENT_TYPE, @@ -67,44 +62,42 @@ where .body(request.into()) .expect("Uri and request headers are valid; qed"); - Some(client.request(request).then(move |response| Ok((response, sender)))) + client + .request(request) + .then(|response| async move { (response, sender) }) }) .buffer_unordered(max_parallel) - .for_each(|(result, sender)| { - let future = match result { + .for_each(|(response, sender)| async { + let result = match response { Ok(ref res) if !res.status().is_success() => { log::trace!("http result status {}", res.status()); - A(future::err(RpcError::Other(format_err!( + Err(RpcError::Client(format!( "Unexpected response status code: {}", res.status() - )))) + ))) + } + Err(err) => Err(RpcError::Other(Box::new(err))), + Ok(res) => { + hyper::body::to_bytes(res.into_body()) + .map_err(|e| RpcError::ParseError(e.to_string(), Box::new(e))) + .await } - Ok(res) => B(res - .into_body() - .map_err(|e| RpcError::ParseError(e.to_string(), e.into())) - .concat2()), - Err(err) => A(future::err(RpcError::Other(err.into()))), }; - future.then(|result| { - if let Some(sender) = sender { - let response = result - .and_then(|response| { - let response_str = String::from_utf8_lossy(response.as_ref()).into_owned(); - super::parse_response(&response_str) - }) - .and_then(|r| r.1); - if let Err(err) = sender.send(response) { - log::warn!("Error resuming asynchronous request: {:?}", err); - } + + if let Some(sender) = sender { + let response = result + .and_then(|response| { + let response_str = String::from_utf8_lossy(response.as_ref()).into_owned(); + super::parse_response(&response_str) + }) + .and_then(|r| r.1); + if let Err(err) = sender.send(response) { + log::warn!("Error resuming asynchronous request: {:?}", err); } - Ok(()) - }) + } }); - B(rt::lazy(move || { - rt::spawn(fut.map_err(|e| log::error!("RPC Client error: {:?}", e))); - Ok(TClient::from(sender.into())) - })) + (sender.into(), fut) } #[cfg(test)] @@ -112,11 +105,8 @@ mod tests { use super::*; use crate::*; use assert_matches::assert_matches; - use hyper::rt; use jsonrpc_core::{Error, ErrorCode, IoHandler, Params, Value}; use jsonrpc_http_server::*; - use std::net::SocketAddr; - use std::time::Duration; fn id(t: T) -> T { t @@ -124,7 +114,6 @@ mod tests { struct TestServer { uri: String, - socket_addr: SocketAddr, server: Option, } @@ -133,28 +122,14 @@ mod tests { let builder = ServerBuilder::new(io()).rest_api(RestApi::Unsecure); let server = alter(builder).start_http(&"127.0.0.1:0".parse().unwrap()).unwrap(); - let socket_addr = server.address().clone(); - let uri = format!("http://{}", socket_addr); + let uri = format!("http://{}", server.address()); TestServer { uri, - socket_addr, server: Some(server), } } - fn start(&mut self) { - if self.server.is_none() { - let server = ServerBuilder::new(io()) - .rest_api(RestApi::Unsecure) - .start_http(&self.socket_addr) - .unwrap(); - self.server = Some(server); - } else { - panic!("Server already running") - } - } - fn stop(&mut self) { let server = self.server.take(); if let Some(server) = server { @@ -165,11 +140,11 @@ mod tests { fn io() -> IoHandler { let mut io = IoHandler::default(); - io.add_method("hello", |params: Params| match params.parse::<(String,)>() { + io.add_sync_method("hello", |params: Params| match params.parse::<(String,)>() { Ok((msg,)) => Ok(Value::String(format!("hello {}", msg))), _ => Ok(Value::String("world".into())), }); - io.add_method("fail", |_: Params| Err(Error::new(ErrorCode::ServerError(-34)))); + io.add_sync_method("fail", |_: Params| Err(Error::new(ErrorCode::ServerError(-34)))); io.add_notification("notify", |params: Params| { let (value,) = params.parse::<(u64,)>().expect("expected one u64 as param"); assert_eq!(value, 12); @@ -188,13 +163,13 @@ mod tests { } impl TestClient { - fn hello(&self, msg: &'static str) -> impl Future { + fn hello(&self, msg: &'static str) -> impl Future> { self.0.call_method("hello", "String", (msg,)) } - fn fail(&self) -> impl Future { + fn fail(&self) -> impl Future> { self.0.call_method("fail", "()", ()) } - fn notify(&self, value: u64) -> impl Future { + fn notify(&self, value: u64) -> RpcResult<()> { self.0.notify("notify", (value,)) } } @@ -205,24 +180,18 @@ mod tests { // given let server = TestServer::serve(id); - let (tx, rx) = std::sync::mpsc::channel(); // when - let run = connect(&server.uri) - .and_then(|client: TestClient| { - client.hello("http").and_then(move |result| { - drop(client); - let _ = tx.send(result); - Ok(()) - }) - }) - .map_err(|e| log::error!("RPC Client error: {:?}", e)); + let run = async { + let client: TestClient = connect(&server.uri).await?; + let result = client.hello("http").await?; - rt::run(run); + // then + assert_eq!("hello http", result); + Ok(()) as RpcResult<_> + }; - // then - let result = rx.recv_timeout(Duration::from_secs(3)).unwrap(); - assert_eq!("hello http", result); + tokio::runtime::Runtime::new().unwrap().block_on(run).unwrap(); } #[test] @@ -231,23 +200,16 @@ mod tests { // given let server = TestServer::serve(id); - let (tx, rx) = std::sync::mpsc::channel(); // when - let run = connect(&server.uri) - .and_then(|client: TestClient| { - client.notify(12).and_then(move |result| { - drop(client); - let _ = tx.send(result); - Ok(()) - }) - }) - .map_err(|e| log::error!("RPC Client error: {:?}", e)); - - rt::run(run); - - // then - rx.recv_timeout(Duration::from_secs(3)).unwrap(); + let run = async { + let client: TestClient = connect(&server.uri).await.unwrap(); + client.notify(12).unwrap(); + }; + + tokio::runtime::Runtime::new().unwrap().block_on(run); + // Ensure that server has not been moved into runtime + drop(server); } #[test] @@ -258,8 +220,8 @@ mod tests { let invalid_uri = "invalid uri"; // when - let run = connect(invalid_uri); // rx.recv_timeout(Duration::from_secs(3)).unwrap(); - let res: Result = run.wait(); + let fut = connect(invalid_uri); + let res: RpcResult = tokio::runtime::Runtime::new().unwrap().block_on(fut); // then assert_matches!( @@ -275,22 +237,15 @@ mod tests { // given let server = TestServer::serve(id); - let (tx, rx) = std::sync::mpsc::channel(); // when - let run = connect(&server.uri) - .and_then(|client: TestClient| { - client.fail().then(move |res| { - let _ = tx.send(res); - Ok(()) - }) - }) - .map_err(|e| log::error!("RPC Client error: {:?}", e)); - rt::run(run); + let run = async { + let client: TestClient = connect(&server.uri).await?; + client.fail().await + }; + let res = tokio::runtime::Runtime::new().unwrap().block_on(run); // then - let res = rx.recv_timeout(Duration::from_secs(3)).unwrap(); - if let Err(RpcError::JsonRpcError(err)) = res { assert_eq!( err, @@ -311,75 +266,24 @@ mod tests { let mut server = TestServer::serve(id); // stop server so that we get a connection refused server.stop(); - let (tx, rx) = std::sync::mpsc::channel(); - let client = connect(&server.uri); + let run = async { + let client: TestClient = connect(&server.uri).await?; + let res = client.hello("http").await; - let call = client - .and_then(|client: TestClient| { - client.hello("http").then(move |res| { - let _ = tx.send(res); - Ok(()) - }) - }) - .map_err(|e| log::error!("RPC Client error: {:?}", e)); - - rt::run(call); - - // then - let res = rx.recv_timeout(Duration::from_secs(3)).unwrap(); - - if let Err(RpcError::Other(err)) = res { - if let Some(err) = err.downcast_ref::() { - assert!(err.is_connect(), format!("Expected Connection Error, got {:?}", err)) + if let Err(RpcError::Other(err)) = res { + if let Some(err) = err.downcast_ref::() { + assert!(err.is_connect(), "Expected Connection Error, got {:?}", err) + } else { + panic!("Expected a hyper::Error") + } } else { - panic!("Expected a hyper::Error") + panic!("Expected JsonRpcError. Received {:?}", res) } - } else { - panic!("Expected JsonRpcError. Received {:?}", res) - } - } - - #[test] - #[ignore] // todo: [AJ] make it pass - fn client_still_works_after_http_connect_error() { - // given - let mut server = TestServer::serve(id); - - // stop server so that we get a connection refused - server.stop(); - let (tx, rx) = std::sync::mpsc::channel(); - let tx2 = tx.clone(); + Ok(()) as RpcResult<_> + }; - let client = connect(&server.uri); - - let call = client - .and_then(move |client: TestClient| { - client - .hello("http") - .then(move |res| { - let _ = tx.send(res); - Ok(()) - }) - .and_then(move |_| { - server.start(); // todo: make the server start on the main thread - client.hello("http2").then(move |res| { - let _ = tx2.send(res); - Ok(()) - }) - }) - }) - .map_err(|e| log::error!("RPC Client error: {:?}", e)); - - // when - rt::run(call); - - let res = rx.recv_timeout(Duration::from_secs(3)).unwrap(); - assert!(res.is_err()); - - // then - let result = rx.recv_timeout(Duration::from_secs(3)).unwrap().unwrap(); - assert_eq!("hello http", result); + tokio::runtime::Runtime::new().unwrap().block_on(run).unwrap(); } } diff --git a/core-client/transports/src/transports/ipc.rs b/core-client/transports/src/transports/ipc.rs index 8d3407489..d50022597 100644 --- a/core-client/transports/src/transports/ipc.rs +++ b/core-client/transports/src/transports/ipc.rs @@ -3,30 +3,34 @@ use crate::transports::duplex::duplex; use crate::{RpcChannel, RpcError}; -use futures::prelude::*; +use futures::{SinkExt, StreamExt, TryStreamExt}; use jsonrpc_server_utils::codecs::StreamCodec; -use parity_tokio_ipc::IpcConnection; -use std::io; +use jsonrpc_server_utils::tokio; +use jsonrpc_server_utils::tokio_util::codec::Decoder as _; +use parity_tokio_ipc::Endpoint; use std::path::Path; -use tokio::codec::Decoder; /// Connect to a JSON-RPC IPC server. -pub fn connect, Client: From>( - path: P, - reactor: &tokio::reactor::Handle, -) -> Result, io::Error> { - let connection = IpcConnection::connect(path, reactor)?; - - Ok(futures::lazy(move || { - let (sink, stream) = StreamCodec::stream_incoming().framed(connection).split(); - let sink = sink.sink_map_err(|e| RpcError::Other(e.into())); - let stream = stream.map_err(|e| RpcError::Other(e.into())); - - let (client, sender) = duplex(sink, stream); - - tokio::spawn(client.map_err(|e| log::warn!("IPC client error: {:?}", e))); - Ok(sender.into()) - })) +pub async fn connect, Client: From>(path: P) -> Result { + let connection = Endpoint::connect(path) + .await + .map_err(|e| RpcError::Other(Box::new(e)))?; + let (sink, stream) = StreamCodec::stream_incoming().framed(connection).split(); + let sink = sink.sink_map_err(|e| RpcError::Other(Box::new(e))); + let stream = stream.map_err(|e| log::error!("IPC stream error: {}", e)); + + let (client, sender) = duplex( + Box::pin(sink), + Box::pin( + stream + .take_while(|x| futures::future::ready(x.is_ok())) + .map(|x| x.expect("Stream is closed upon first error.")), + ), + ); + + tokio::spawn(client); + + Ok(sender.into()) } #[cfg(test)] @@ -37,17 +41,13 @@ mod tests { use jsonrpc_ipc_server::ServerBuilder; use parity_tokio_ipc::dummy_endpoint; use serde_json::map::Map; - use tokio::runtime::Runtime; #[test] fn should_call_one() { - let mut rt = Runtime::new().unwrap(); - #[allow(deprecated)] - let reactor = rt.reactor().clone(); let sock_path = dummy_endpoint(); let mut io = IoHandler::new(); - io.add_method("greeting", |params| { + io.add_method("greeting", |params| async { let map_obj = match params { Params::Map(obj) => obj, _ => return Err(Error::invalid_params("missing object")), @@ -58,66 +58,57 @@ mod tests { }; Ok(Value::String(format!("Hello {}!", name))) }); - let builder = ServerBuilder::new(io).event_loop_executor(rt.executor()); - let server = builder.start(&sock_path).expect("Couldn't open socket"); - - let client: RawClient = rt.block_on(connect(sock_path, &reactor).unwrap()).unwrap(); - let mut map = Map::new(); - map.insert("name".to_string(), "Jeffry".into()); - let fut = client.call_method("greeting", Params::Map(map)); - - // FIXME: it seems that IPC server on Windows won't be polled with - // default I/O reactor, work around with sending stop signal which polls - // the server (https://github.com/paritytech/jsonrpc/pull/459) - server.close(); - - match rt.block_on(fut) { - Ok(val) => assert_eq!(&val, "Hello Jeffry!"), - Err(err) => panic!("IPC RPC call failed: {}", err), - } - rt.shutdown_now().wait().unwrap(); + let builder = ServerBuilder::new(io); + let _server = builder.start(&sock_path).expect("Couldn't open socket"); + + let client_fut = async move { + let client: RawClient = connect(sock_path).await.unwrap(); + let mut map = Map::new(); + map.insert("name".to_string(), "Jeffry".into()); + let fut = client.call_method("greeting", Params::Map(map)); + + match fut.await { + Ok(val) => assert_eq!(&val, "Hello Jeffry!"), + Err(err) => panic!("IPC RPC call failed: {}", err), + } + }; + tokio::runtime::Runtime::new().unwrap().block_on(client_fut); } #[test] fn should_fail_without_server() { - let rt = Runtime::new().unwrap(); - #[allow(deprecated)] - let reactor = rt.reactor(); - - match connect::<_, RawClient>(dummy_endpoint(), reactor) { - Err(..) => {} - Ok(..) => panic!("Should not be able to connect to an IPC socket that's not open"), - } - rt.shutdown_now().wait().unwrap(); + let test_fut = async move { + match connect::<_, RawClient>(dummy_endpoint()).await { + Err(..) => {} + Ok(..) => panic!("Should not be able to connect to an IPC socket that's not open"), + } + }; + + tokio::runtime::Runtime::new().unwrap().block_on(test_fut); } #[test] fn should_handle_server_error() { - let mut rt = Runtime::new().unwrap(); - #[allow(deprecated)] - let reactor = rt.reactor().clone(); let sock_path = dummy_endpoint(); let mut io = IoHandler::new(); - io.add_method("greeting", |_params| Err(Error::invalid_params("test error"))); - let builder = ServerBuilder::new(io).event_loop_executor(rt.executor()); - let server = builder.start(&sock_path).expect("Couldn't open socket"); - - let client: RawClient = rt.block_on(connect(sock_path, &reactor).unwrap()).unwrap(); - let mut map = Map::new(); - map.insert("name".to_string(), "Jeffry".into()); - let fut = client.call_method("greeting", Params::Map(map)); - - // FIXME: it seems that IPC server on Windows won't be polled with - // default I/O reactor, work around with sending stop signal which polls - // the server (https://github.com/paritytech/jsonrpc/pull/459) - server.close(); - - match rt.block_on(fut) { - Err(RpcError::JsonRpcError(err)) => assert_eq!(err.code, ErrorCode::InvalidParams), - Ok(_) => panic!("Expected the call to fail"), - _ => panic!("Unexpected error type"), - } - rt.shutdown_now().wait().unwrap(); + io.add_method("greeting", |_params| async { Err(Error::invalid_params("test error")) }); + let builder = ServerBuilder::new(io); + let _server = builder.start(&sock_path).expect("Couldn't open socket"); + + let client_fut = async move { + let client: RawClient = connect(sock_path).await.unwrap(); + let mut map = Map::new(); + map.insert("name".to_string(), "Jeffry".into()); + let fut = client.call_method("greeting", Params::Map(map)); + + match fut.await { + Err(RpcError::JsonRpcError(err)) => assert_eq!(err.code, ErrorCode::InvalidParams), + Ok(_) => panic!("Expected the call to fail"), + _ => panic!("Unexpected error type"), + } + }; + + tokio::runtime::Runtime::new().unwrap().block_on(client_fut); } } diff --git a/core-client/transports/src/transports/local.rs b/core-client/transports/src/transports/local.rs index 5dd1dc73c..da91a554b 100644 --- a/core-client/transports/src/transports/local.rs +++ b/core-client/transports/src/transports/local.rs @@ -1,26 +1,36 @@ //! Rpc client implementation for `Deref>`. -use crate::{RpcChannel, RpcError}; -use failure::format_err; -use futures::prelude::*; -use futures::sync::mpsc; -use jsonrpc_core::{MetaIoHandler, Metadata}; +use crate::{RpcChannel, RpcError, RpcResult}; +use futures::channel::mpsc; +use futures::{ + task::{Context, Poll}, + Future, Sink, SinkExt, Stream, StreamExt, +}; +use jsonrpc_core::{BoxFuture, MetaIoHandler, Metadata, Middleware}; use jsonrpc_pubsub::Session; -use std::collections::VecDeque; use std::ops::Deref; +use std::pin::Pin; use std::sync::Arc; /// Implements a rpc client for `MetaIoHandler`. pub struct LocalRpc { handler: THandler, meta: TMetadata, - queue: VecDeque, + buffered: Buffered, + queue: (mpsc::UnboundedSender, mpsc::UnboundedReceiver), } -impl LocalRpc +enum Buffered { + Request(BoxFuture>), + Response(String), + None, +} + +impl LocalRpc where TMetadata: Metadata, - THandler: Deref>, + TMiddleware: Middleware, + THandler: Deref>, { /// Creates a new `LocalRpc` with default metadata. pub fn new(handler: THandler) -> Self @@ -35,88 +45,165 @@ where Self { handler, meta, - queue: Default::default(), + buffered: Buffered::None, + queue: mpsc::unbounded(), } } } -impl Stream for LocalRpc +impl Stream for LocalRpc where - TMetadata: Metadata, - THandler: Deref>, + TMetadata: Metadata + Unpin, + TMiddleware: Middleware + Unpin, + THandler: Deref> + Unpin, { type Item = String; - type Error = RpcError; - fn poll(&mut self) -> Result>, Self::Error> { - match self.queue.pop_front() { - Some(response) => Ok(Async::Ready(Some(response))), - None => Ok(Async::NotReady), + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.queue.1.poll_next_unpin(cx) + } +} + +impl LocalRpc +where + TMetadata: Metadata + Unpin, + TMiddleware: Middleware + Unpin, + THandler: Deref> + Unpin, +{ + fn poll_buffered(&mut self, cx: &mut Context) -> Poll> { + let response = match self.buffered { + Buffered::Request(ref mut r) => futures::ready!(r.as_mut().poll(cx)), + _ => None, + }; + if let Some(response) = response { + self.buffered = Buffered::Response(response); + } + + self.send_response().into() + } + + fn send_response(&mut self) -> Result<(), RpcError> { + if let Buffered::Response(r) = std::mem::replace(&mut self.buffered, Buffered::None) { + self.queue.0.start_send(r).map_err(|e| RpcError::Other(Box::new(e)))?; } + Ok(()) } } -impl Sink for LocalRpc +impl Sink for LocalRpc where - TMetadata: Metadata, - THandler: Deref>, + TMetadata: Metadata + Unpin, + TMiddleware: Middleware + Unpin, + THandler: Deref> + Unpin, { - type SinkItem = String; - type SinkError = RpcError; + type Error = RpcError; - fn start_send(&mut self, request: Self::SinkItem) -> Result, Self::SinkError> { - match self.handler.handle_request_sync(&request, self.meta.clone()) { - Some(response) => self.queue.push_back(response), - None => {} - }; - Ok(AsyncSink::Ready) + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + futures::ready!(self.poll_buffered(cx))?; + futures::ready!(self.queue.0.poll_ready(cx)) + .map_err(|e| RpcError::Other(Box::new(e))) + .into() + } + + fn start_send(mut self: Pin<&mut Self>, item: String) -> Result<(), Self::Error> { + let future = self.handler.handle_request(&item, self.meta.clone()); + self.buffered = Buffered::Request(Box::pin(future)); + Ok(()) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + futures::ready!(self.poll_buffered(cx))?; + futures::ready!(self.queue.0.poll_flush_unpin(cx)) + .map_err(|e| RpcError::Other(Box::new(e))) + .into() } - fn poll_complete(&mut self) -> Result, Self::SinkError> { - Ok(Async::Ready(())) + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + futures::ready!(self.queue.0.poll_close_unpin(cx)) + .map_err(|e| RpcError::Other(Box::new(e))) + .into() } } -/// Connects to a `Deref`. -pub fn connect_with_metadata( +/// Connects to a `Deref` specifying a custom middleware implementation. +pub fn connect_with_metadata_and_middleware( handler: THandler, meta: TMetadata, -) -> (TClient, impl Future) +) -> (TClient, impl Future>) where TClient: From, - THandler: Deref>, - TMetadata: Metadata, + TMiddleware: Middleware + Unpin, + THandler: Deref> + Unpin, + TMetadata: Metadata + Unpin, { let (sink, stream) = LocalRpc::with_metadata(handler, meta).split(); - let (rpc_client, sender) = crate::transports::duplex(sink, stream); + let (rpc_client, sender) = crate::transports::duplex(Box::pin(sink), Box::pin(stream)); let client = TClient::from(sender); (client, rpc_client) } +/// Connects to a `Deref`. +pub fn connect_with_metadata( + handler: THandler, + meta: TMetadata, +) -> (TClient, impl Future>) +where + TClient: From, + TMetadata: Metadata + Unpin, + THandler: Deref> + Unpin, +{ + connect_with_metadata_and_middleware(handler, meta) +} + +/// Connects to a `Deref` specifying a custom middleware implementation. +pub fn connect_with_middleware( + handler: THandler, +) -> (TClient, impl Future>) +where + TClient: From, + TMetadata: Metadata + Default + Unpin, + TMiddleware: Middleware + Unpin, + THandler: Deref> + Unpin, +{ + connect_with_metadata_and_middleware(handler, Default::default()) +} + /// Connects to a `Deref`. -pub fn connect(handler: THandler) -> (TClient, impl Future) +pub fn connect(handler: THandler) -> (TClient, impl Future>) where TClient: From, - THandler: Deref>, - TMetadata: Metadata + Default, + TMetadata: Metadata + Default + Unpin, + THandler: Deref> + Unpin, { - connect_with_metadata(handler, Default::default()) + connect_with_middleware(handler) } /// Metadata for LocalRpc. pub type LocalMeta = Arc; -/// Connects with pubsub. -pub fn connect_with_pubsub(handler: THandler) -> (TClient, impl Future) +/// Connects with pubsub specifying a custom middleware implementation. +pub fn connect_with_pubsub_and_middleware( + handler: THandler, +) -> (TClient, impl Future>) where TClient: From, - THandler: Deref>, + TMiddleware: Middleware + Unpin, + THandler: Deref> + Unpin, { - let (tx, rx) = mpsc::channel(0); + let (tx, rx) = mpsc::unbounded(); let meta = Arc::new(Session::new(tx)); let (sink, stream) = LocalRpc::with_metadata(handler, meta).split(); - let stream = stream.select(rx.map_err(|_| RpcError::Other(format_err!("Pubsub channel returned an error")))); - let (rpc_client, sender) = crate::transports::duplex(sink, stream); + let stream = futures::stream::select(stream, rx); + let (rpc_client, sender) = crate::transports::duplex(Box::pin(sink), Box::pin(stream)); let client = TClient::from(sender); (client, rpc_client) } + +/// Connects with pubsub. +pub fn connect_with_pubsub(handler: THandler) -> (TClient, impl Future>) +where + TClient: From, + THandler: Deref> + Unpin, +{ + connect_with_pubsub_and_middleware(handler) +} diff --git a/core-client/transports/src/transports/mod.rs b/core-client/transports/src/transports/mod.rs index fdf54d74a..00b6f0600 100644 --- a/core-client/transports/src/transports/mod.rs +++ b/core-client/transports/src/transports/mod.rs @@ -82,7 +82,7 @@ pub fn parse_response( response: &str, ) -> Result<(Id, Result, Option, Option), RpcError> { jsonrpc_core::serde_from_str::(response) - .map_err(|e| RpcError::ParseError(e.to_string(), e.into())) + .map_err(|e| RpcError::ParseError(e.to_string(), Box::new(e))) .map(|response| { let id = response.id().unwrap_or(Id::Null); let sid = response.subscription_id(); diff --git a/core-client/transports/src/transports/ws.rs b/core-client/transports/src/transports/ws.rs index e9f89531d..976d48748 100644 --- a/core-client/transports/src/transports/ws.rs +++ b/core-client/transports/src/transports/ws.rs @@ -1,28 +1,29 @@ //! JSON-RPC websocket client implementation. -use crate::{RpcChannel, RpcError}; -use failure::Error; -use futures::prelude::*; -use log::info; use std::collections::VecDeque; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use crate::{RpcChannel, RpcError}; use websocket::{ClientBuilder, OwnedMessage}; /// Connect to a JSON-RPC websocket server. /// -/// Uses an unbuffered channel to queue outgoing rpc messages. +/// Uses an unbounded channel to queue outgoing rpc messages. /// /// Returns `Err` if the `url` is invalid. -pub fn try_connect(url: &str) -> Result, Error> +pub fn try_connect(url: &str) -> Result>, RpcError> where T: From, { - let client_builder = ClientBuilder::new(url)?; + let client_builder = ClientBuilder::new(url).map_err(|e| RpcError::Other(Box::new(e)))?; Ok(do_connect(client_builder)) } /// Connect to a JSON-RPC websocket server. /// -/// Uses an unbuffered channel to queue outgoing rpc messages. -pub fn connect(url: &url::Url) -> impl Future +/// Uses an unbounded channel to queue outgoing rpc messages. +pub fn connect(url: &url::Url) -> impl Future> where T: From, { @@ -30,21 +31,38 @@ where do_connect(client_builder) } -fn do_connect(client_builder: ClientBuilder) -> impl Future +fn do_connect(client_builder: ClientBuilder) -> impl Future> where T: From, { + use futures::compat::{Future01CompatExt, Sink01CompatExt, Stream01CompatExt}; + use futures::{SinkExt, StreamExt, TryFutureExt, TryStreamExt}; + use websocket::futures::Stream; + client_builder .async_connect(None) - .map(|(client, _)| { + .compat() + .map_err(|error| RpcError::Other(Box::new(error))) + .map_ok(|(client, _)| { let (sink, stream) = client.split(); + + let sink = sink.sink_compat().sink_map_err(|e| RpcError::Other(Box::new(e))); + let stream = stream.compat().map_err(|e| RpcError::Other(Box::new(e))); let (sink, stream) = WebsocketClient::new(sink, stream).split(); + let (sink, stream) = ( + Box::pin(sink), + Box::pin( + stream + .take_while(|x| futures::future::ready(x.is_ok())) + .map(|x| x.expect("Stream is closed upon first error.")), + ), + ); let (rpc_client, sender) = super::duplex(sink, stream); - let rpc_client = rpc_client.map_err(|error| eprintln!("{:?}", error)); + let rpc_client = rpc_client.map_err(|error| log::error!("{:?}", error)); tokio::spawn(rpc_client); + sender.into() }) - .map_err(|error| RpcError::Other(error.into())) } struct WebsocketClient { @@ -55,9 +73,9 @@ struct WebsocketClient { impl WebsocketClient where - TSink: Sink, - TStream: Stream, - TError: Into, + TSink: futures::Sink + Unpin, + TStream: futures::Stream> + Unpin, + TError: std::error::Error + Send + 'static, { pub fn new(sink: TSink, stream: TStream) -> Self { Self { @@ -66,65 +84,116 @@ where queue: VecDeque::new(), } } + + // Drains the internal buffer and attempts to forward as much of the items + // as possible to the underlying sink + fn try_empty_buffer(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = Pin::into_inner(self); + + match Pin::new(&mut this.sink).poll_ready(cx) { + Poll::Ready(value) => value?, + Poll::Pending => return Poll::Pending, + } + + while let Some(item) = this.queue.pop_front() { + Pin::new(&mut this.sink).start_send(item)?; + + if !this.queue.is_empty() { + match Pin::new(&mut this.sink).poll_ready(cx) { + Poll::Ready(value) => value?, + Poll::Pending => return Poll::Pending, + } + } + } + + Poll::Ready(Ok(())) + } } -impl Sink for WebsocketClient +// This mostly forwards to the underlying sink but also adds an unbounded queue +// for when the underlying sink is incapable of receiving more items. +// See https://docs.rs/futures-util/0.3.8/futures_util/sink/struct.Buffer.html +// for the variant with a fixed-size buffer. +impl futures::Sink for WebsocketClient where - TSink: Sink, - TStream: Stream, - TError: Into, + TSink: futures::Sink + Unpin, + TStream: futures::Stream> + Unpin, { - type SinkItem = String; - type SinkError = RpcError; + type Error = RpcError; + + fn start_send(mut self: Pin<&mut Self>, request: String) -> Result<(), Self::Error> { + let request = OwnedMessage::Text(request); - fn start_send(&mut self, request: Self::SinkItem) -> Result, Self::SinkError> { - self.queue.push_back(OwnedMessage::Text(request)); - Ok(AsyncSink::Ready) + if self.queue.is_empty() { + let this = Pin::into_inner(self); + Pin::new(&mut this.sink).start_send(request) + } else { + self.queue.push_back(request); + Ok(()) + } } - fn poll_complete(&mut self) -> Result, Self::SinkError> { - loop { - match self.queue.pop_front() { - Some(request) => match self.sink.start_send(request) { - Ok(AsyncSink::Ready) => continue, - Ok(AsyncSink::NotReady(request)) => { - self.queue.push_front(request); - break; - } - Err(error) => return Err(RpcError::Other(error.into())), - }, - None => break, - } + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = Pin::into_inner(self); + + if this.queue.is_empty() { + return Pin::new(&mut this.sink).poll_ready(cx); + } + + let _ = Pin::new(this).try_empty_buffer(cx)?; + + Poll::Ready(Ok(())) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = Pin::into_inner(self); + + match Pin::new(&mut *this).try_empty_buffer(cx) { + Poll::Ready(value) => value?, + Poll::Pending => return Poll::Pending, } - self.sink.poll_complete().map_err(|error| RpcError::Other(error.into())) + debug_assert!(this.queue.is_empty()); + + Pin::new(&mut this.sink).poll_flush(cx) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = Pin::into_inner(self); + + match Pin::new(&mut *this).try_empty_buffer(cx) { + Poll::Ready(value) => value?, + Poll::Pending => return Poll::Pending, + } + debug_assert!(this.queue.is_empty()); + + Pin::new(&mut this.sink).poll_close(cx) } } -impl Stream for WebsocketClient +impl futures::Stream for WebsocketClient where - TSink: Sink, - TStream: Stream, - TError: Into, + TSink: futures::Sink + Unpin, + TStream: futures::Stream> + Unpin, { - type Item = String; - type Error = RpcError; + type Item = Result; - fn poll(&mut self) -> Result>, Self::Error> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = Pin::into_inner(self); loop { - match self.stream.poll() { - Ok(Async::Ready(Some(message))) => match message { - OwnedMessage::Text(data) => return Ok(Async::Ready(Some(data))), - OwnedMessage::Binary(data) => info!("server sent binary data {:?}", data), - OwnedMessage::Ping(p) => self.queue.push_front(OwnedMessage::Pong(p)), + match Pin::new(&mut this.stream).poll_next(cx) { + Poll::Ready(Some(Ok(message))) => match message { + OwnedMessage::Text(data) => return Poll::Ready(Some(Ok(data))), + OwnedMessage::Binary(data) => log::info!("server sent binary data {:?}", data), + OwnedMessage::Ping(p) => this.queue.push_front(OwnedMessage::Pong(p)), OwnedMessage::Pong(_) => {} - OwnedMessage::Close(c) => self.queue.push_front(OwnedMessage::Close(c)), + OwnedMessage::Close(c) => this.queue.push_front(OwnedMessage::Close(c)), }, - Ok(Async::Ready(None)) => { + Poll::Ready(None) => { // TODO try to reconnect (#411). - return Ok(Async::Ready(None)); + return Poll::Ready(None); } - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(error) => return Err(RpcError::Other(error.into())), + Poll::Pending => return Poll::Pending, + Poll::Ready(Some(Err(error))) => return Poll::Ready(Some(Err(RpcError::Other(Box::new(error))))), } } } diff --git a/core/Cargo.toml b/core/Cargo.toml index f4bd56fec..39e2de93d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -8,7 +8,7 @@ keywords = ["jsonrpc", "json-rpc", "json", "rpc", "serde"] license = "MIT" name = "jsonrpc-core" repository = "https://github.com/paritytech/jsonrpc" -version = "14.2.0" +version = "17.1.0" categories = [ "asynchronous", @@ -20,12 +20,18 @@ categories = [ [dependencies] log = "0.4" -futures = "~0.1.6" +# FIXME: Currently a lot of jsonrpc-* crates depend on entire `futures` being +# re-exported but it's not strictly required for this crate. Either adapt the +# remaining crates or settle for this re-export to be a single, common dependency +futures = { version = "0.3", optional = true } +futures-util = { version = "0.3", default-features = false, features = ["std"] } +futures-executor = { version = "0.3", optional = true } serde = "1.0" serde_json = "1.0" serde_derive = "1.0" [features] +default = ["futures-executor", "futures"] arbitrary_precision = ["serde_json/arbitrary_precision"] [badges] diff --git a/core/README.md b/core/README.md deleted file mode 100644 index 13f5e9f64..000000000 --- a/core/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# jsonrpc-core -Transport agnostic rust implementation of JSON-RPC 2.0 Specification. - -[Documentation](http://paritytech.github.io/jsonrpc/jsonrpc_core/index.html) - -- [x] - server side -- [x] - client side - -## Example - -`Cargo.toml` - - -``` -[dependencies] -jsonrpc-core = "4.0" -``` - -`main.rs` - -```rust -use jsonrpc_core::*; - -fn main() { - let mut io = IoHandler::default(); - io.add_method("say_hello", |_params: Params| { - Ok(Value::String("hello".into())) - }); - - let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":"hello","id":1}"#; - - assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); -} -``` - -### Asynchronous responses - -`main.rs` - -```rust -use jsonrpc_core::*; -use jsonrpc_core::futures::Future; - -fn main() { - let io = IoHandler::new(); - io.add_async_method("say_hello", |_params: Params| { - futures::finished(Value::String("hello".into())) - }); - - let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":"hello","id":1}"#; - - assert_eq!(io.handle_request(request).wait().unwrap(), Some(response.to_owned())); -} -``` - -### Publish-Subscribe -See examples directory. diff --git a/core/examples/async.rs b/core/examples/async.rs index 619cd3742..b6397b240 100644 --- a/core/examples/async.rs +++ b/core/examples/async.rs @@ -1,15 +1,16 @@ -use jsonrpc_core::futures::Future; use jsonrpc_core::*; fn main() { - let mut io = IoHandler::new(); + futures_executor::block_on(async { + let mut io = IoHandler::new(); - io.add_method("say_hello", |_: Params| { - futures::finished(Value::String("Hello World!".to_owned())) - }); + io.add_method("say_hello", |_: Params| async { + Ok(Value::String("Hello World!".to_owned())) + }); - let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":"hello","id":1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":"hello","id":1}"#; - assert_eq!(io.handle_request(request).wait().unwrap(), Some(response.to_owned())); + assert_eq!(io.handle_request(request).await, Some(response.to_owned())); + }); } diff --git a/core/examples/basic.rs b/core/examples/basic.rs index 24dbbca1b..f81c24b12 100644 --- a/core/examples/basic.rs +++ b/core/examples/basic.rs @@ -3,7 +3,7 @@ use jsonrpc_core::*; fn main() { let mut io = IoHandler::new(); - io.add_method("say_hello", |_: Params| Ok(Value::String("Hello World!".to_owned()))); + io.add_sync_method("say_hello", |_: Params| Ok(Value::String("Hello World!".to_owned()))); let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"hello","id":1}"#; diff --git a/core/examples/meta.rs b/core/examples/meta.rs index 3e19a4bdf..abc3c6b55 100644 --- a/core/examples/meta.rs +++ b/core/examples/meta.rs @@ -1,4 +1,3 @@ -use jsonrpc_core::futures::Future; use jsonrpc_core::*; #[derive(Clone, Default)] @@ -8,7 +7,7 @@ impl Metadata for Meta {} pub fn main() { let mut io = MetaIoHandler::default(); - io.add_method_with_meta("say_hello", |_params: Params, meta: Meta| { + io.add_method_with_meta("say_hello", |_params: Params, meta: Meta| async move { Ok(Value::String(format!("Hello World: {}", meta.0))) }); @@ -17,7 +16,7 @@ pub fn main() { let headers = 5; assert_eq!( - io.handle_request(request, Meta(headers)).wait().unwrap(), + io.handle_request_sync(request, Meta(headers)), Some(response.to_owned()) ); } diff --git a/core/examples/middlewares.rs b/core/examples/middlewares.rs index 48af3e595..6d25352af 100644 --- a/core/examples/middlewares.rs +++ b/core/examples/middlewares.rs @@ -1,6 +1,6 @@ -use jsonrpc_core::futures::future::Either; -use jsonrpc_core::futures::Future; +use jsonrpc_core::futures_util::{future::Either, FutureExt}; use jsonrpc_core::*; +use std::future::Future; use std::sync::atomic::{self, AtomicUsize}; use std::time::Instant; @@ -17,13 +17,13 @@ impl Middleware for MyMiddleware { fn on_request(&self, request: Request, meta: Meta, next: F) -> Either where F: FnOnce(Request, Meta) -> X + Send, - X: Future, Error = ()> + Send + 'static, + X: Future> + Send + 'static, { let start = Instant::now(); let request_number = self.0.fetch_add(1, atomic::Ordering::SeqCst); println!("Processing request {}: {:?}, {:?}", request_number, request, meta); - Either::A(Box::new(next(request, meta).map(move |res| { + Either::Left(Box::pin(next(request, meta).map(move |res| { println!("Processing took: {:?}", start.elapsed()); res }))) @@ -33,7 +33,7 @@ impl Middleware for MyMiddleware { pub fn main() { let mut io = MetaIoHandler::with_middleware(MyMiddleware::default()); - io.add_method_with_meta("say_hello", |_params: Params, meta: Meta| { + io.add_method_with_meta("say_hello", |_params: Params, meta: Meta| async move { Ok(Value::String(format!("Hello World: {}", meta.0))) }); @@ -42,7 +42,7 @@ pub fn main() { let headers = 5; assert_eq!( - io.handle_request(request, Meta(headers)).wait().unwrap(), + io.handle_request_sync(request, Meta(headers)), Some(response.to_owned()) ); } diff --git a/core/examples/params.rs b/core/examples/params.rs index d528b3578..353ab770a 100644 --- a/core/examples/params.rs +++ b/core/examples/params.rs @@ -9,7 +9,7 @@ struct HelloParams { fn main() { let mut io = IoHandler::new(); - io.add_method("say_hello", |params: Params| { + io.add_method("say_hello", |params: Params| async move { let parsed: HelloParams = params.parse().unwrap(); Ok(Value::String(format!("hello, {}", parsed.name))) }); diff --git a/core/src/calls.rs b/core/src/calls.rs index 335d75fb7..dfc8dcb16 100644 --- a/core/src/calls.rs +++ b/core/src/calls.rs @@ -1,7 +1,7 @@ use crate::types::{Error, Params, Value}; use crate::BoxFuture; -use futures::{Future, IntoFuture}; use std::fmt; +use std::future::Future; use std::sync::Arc; /// Metadata trait @@ -11,10 +11,34 @@ impl Metadata for Option {} impl Metadata for Box {} impl Metadata for Arc {} +/// A future-conversion trait. +pub trait WrapFuture { + /// Convert itself into a boxed future. + fn into_future(self) -> BoxFuture>; +} + +impl WrapFuture for Result { + fn into_future(self) -> BoxFuture> { + Box::pin(async { self }) + } +} + +impl WrapFuture for BoxFuture> { + fn into_future(self) -> BoxFuture> { + self + } +} + +/// A synchronous or asynchronous method. +pub trait RpcMethodSync: Send + Sync + 'static { + /// Call method + fn call(&self, params: Params) -> BoxFuture>; +} + /// Asynchronous Method pub trait RpcMethodSimple: Send + Sync + 'static { /// Output future - type Out: Future + Send; + type Out: Future> + Send; /// Call method fn call(&self, params: Params) -> Self::Out; } @@ -22,7 +46,7 @@ pub trait RpcMethodSimple: Send + Sync + 'static { /// Asynchronous Method with Metadata pub trait RpcMethod: Send + Sync + 'static { /// Call method - fn call(&self, params: Params, meta: T) -> BoxFuture; + fn call(&self, params: Params, meta: T) -> BoxFuture>; } /// Notification @@ -59,14 +83,23 @@ impl fmt::Debug for RemoteProcedure { } } -impl RpcMethodSimple for F +impl RpcMethodSimple for F where - F: Fn(Params) -> I, - X: Future, - I: IntoFuture, + F: Fn(Params) -> X, + X: Future>, { type Out = X; fn call(&self, params: Params) -> Self::Out { + self(params) + } +} + +impl RpcMethodSync for F +where + F: Fn(Params) -> X, + X: WrapFuture, +{ + fn call(&self, params: Params) -> BoxFuture> { self(params).into_future() } } @@ -80,15 +113,14 @@ where } } -impl RpcMethod for F +impl RpcMethod for F where T: Metadata, - F: Fn(Params, T) -> I, - I: IntoFuture, - X: Future, + F: Fn(Params, T) -> X, + X: Future>, { - fn call(&self, params: Params, meta: T) -> BoxFuture { - Box::new(self(params, meta).into_future()) + fn call(&self, params: Params, meta: T) -> BoxFuture> { + Box::pin(self(params, meta)) } } diff --git a/core/src/delegates.rs b/core/src/delegates.rs index 577d9d8d1..15b60e65a 100644 --- a/core/src/delegates.rs +++ b/core/src/delegates.rs @@ -1,12 +1,12 @@ //! Delegate rpc calls use std::collections::HashMap; +use std::future::Future; use std::sync::Arc; use crate::calls::{Metadata, RemoteProcedure, RpcMethod, RpcNotification}; use crate::types::{Error, Params, Value}; use crate::BoxFuture; -use futures::IntoFuture; struct DelegateAsyncMethod { delegate: Arc, @@ -17,14 +17,13 @@ impl RpcMethod for DelegateAsyncMethod where M: Metadata, F: Fn(&T, Params) -> I, - I: IntoFuture, + I: Future> + Send + 'static, T: Send + Sync + 'static, F: Send + Sync + 'static, - I::Future: Send + 'static, { - fn call(&self, params: Params, _meta: M) -> BoxFuture { + fn call(&self, params: Params, _meta: M) -> BoxFuture> { let closure = &self.closure; - Box::new(closure(&self.delegate, params).into_future()) + Box::pin(closure(&self.delegate, params)) } } @@ -37,14 +36,13 @@ impl RpcMethod for DelegateMethodWithMeta where M: Metadata, F: Fn(&T, Params, M) -> I, - I: IntoFuture, + I: Future> + Send + 'static, T: Send + Sync + 'static, F: Send + Sync + 'static, - I::Future: Send + 'static, { - fn call(&self, params: Params, meta: M) -> BoxFuture { + fn call(&self, params: Params, meta: M) -> BoxFuture> { let closure = &self.closure; - Box::new(closure(&self.delegate, params, meta).into_future()) + Box::pin(closure(&self.delegate, params, meta)) } } @@ -117,9 +115,8 @@ where pub fn add_method(&mut self, name: &str, method: F) where F: Fn(&T, Params) -> I, - I: IntoFuture, + I: Future> + Send + 'static, F: Send + Sync + 'static, - I::Future: Send + 'static, { self.methods.insert( name.into(), @@ -134,9 +131,8 @@ where pub fn add_method_with_meta(&mut self, name: &str, method: F) where F: Fn(&T, Params, M) -> I, - I: IntoFuture, + I: Future> + Send + 'static, F: Send + Sync + 'static, - I::Future: Send + 'static, { self.methods.insert( name.into(), diff --git a/core/src/io.rs b/core/src/io.rs index 65f67162b..713b47f97 100644 --- a/core/src/io.rs +++ b/core/src/io.rs @@ -2,38 +2,42 @@ use std::collections::{ hash_map::{IntoIter, Iter}, HashMap, }; +use std::future::Future; use std::ops::{Deref, DerefMut}; +use std::pin::Pin; use std::sync::Arc; -use futures::{self, future, Future}; +use futures_util::{self, future, FutureExt}; use serde_json; -use crate::calls::{Metadata, RemoteProcedure, RpcMethod, RpcMethodSimple, RpcNotification, RpcNotificationSimple}; +use crate::calls::{ + Metadata, RemoteProcedure, RpcMethod, RpcMethodSimple, RpcMethodSync, RpcNotification, RpcNotificationSimple, +}; use crate::middleware::{self, Middleware}; use crate::types::{Call, Output, Request, Response}; use crate::types::{Error, ErrorCode, Version}; /// A type representing middleware or RPC response before serialization. -pub type FutureResponse = Box, Error = ()> + Send>; +pub type FutureResponse = Pin> + Send>>; /// A type representing middleware or RPC call output. -pub type FutureOutput = Box, Error = ()> + Send>; +pub type FutureOutput = Pin> + Send>>; /// A type representing future string response. pub type FutureResult = future::Map< - future::Either, ()>, FutureRpcResult>, + future::Either>, FutureRpcResult>, fn(Option) -> Option, >; /// A type representing a result of a single method call. -pub type FutureRpcOutput = future::Either, ()>>>; +pub type FutureRpcOutput = future::Either>>>; /// A type representing an optional `Response` for RPC `Request`. pub type FutureRpcResult = future::Either< F, future::Either< future::Map, fn(Option) -> Option>, - future::Map>>, fn(Vec>) -> Option>, + future::Map>, fn(Vec>) -> Option>, >, >; @@ -139,7 +143,17 @@ impl> MetaIoHandler { self.methods.insert(alias.into(), RemoteProcedure::Alias(other.into())); } - /// Adds new supported asynchronous method + /// Adds new supported synchronous method. + /// + /// A backward-compatible wrapper. + pub fn add_sync_method(&mut self, name: &str, method: F) + where + F: RpcMethodSync, + { + self.add_method(name, move |params| method.call(params)) + } + + /// Adds new supported asynchronous method. pub fn add_method(&mut self, name: &str, method: F) where F: RpcMethodSimple, @@ -184,15 +198,14 @@ impl> MetaIoHandler { /// Handle given request synchronously - will block until response is available. /// If you have any asynchronous methods in your RPC it is much wiser to use /// `handle_request` instead and deal with asynchronous requests in a non-blocking fashion. + #[cfg(feature = "futures-executor")] pub fn handle_request_sync(&self, request: &str, meta: T) -> Option { - self.handle_request(request, meta) - .wait() - .expect("Handler calls can never fail.") + futures_executor::block_on(self.handle_request(request, meta)) } /// Handle given request asynchronously. pub fn handle_request(&self, request: &str, meta: T) -> FutureResult { - use self::future::Either::{A, B}; + use self::future::Either::{Left, Right}; fn as_string(response: Option) -> Option { let res = response.map(write_response); debug!(target: "rpc", "Response: {}.", match res { @@ -205,11 +218,11 @@ impl> MetaIoHandler { trace!(target: "rpc", "Request: {}.", request); let request = read_request(request); let result = match request { - Err(error) => A(futures::finished(Some(Response::from( + Err(error) => Left(future::ready(Some(Response::from( error, self.compatibility.default_version(), )))), - Ok(request) => B(self.handle_rpc_request(request, meta)), + Ok(request) => Right(self.handle_rpc_request(request, meta)), }; result.map(as_string) @@ -217,7 +230,7 @@ impl> MetaIoHandler { /// Handle deserialized RPC request. pub fn handle_rpc_request(&self, request: Request, meta: T) -> FutureRpcResult { - use self::future::Either::{A, B}; + use self::future::Either::{Left, Right}; fn output_as_response(output: Option) -> Option { output.map(Response::Single) @@ -234,23 +247,25 @@ impl> MetaIoHandler { self.middleware .on_request(request, meta, |request, meta| match request { - Request::Single(call) => A(self - .handle_call(call, meta) - .map(output_as_response as fn(Option) -> Option)), + Request::Single(call) => Left( + self.handle_call(call, meta) + .map(output_as_response as fn(Option) -> Option), + ), Request::Batch(calls) => { let futures: Vec<_> = calls .into_iter() .map(move |call| self.handle_call(call, meta.clone())) .collect(); - B(futures::future::join_all(futures) - .map(outputs_as_batch as fn(Vec>) -> Option)) + Right( + future::join_all(futures).map(outputs_as_batch as fn(Vec>) -> Option), + ) } }) } /// Handle single call asynchronously. pub fn handle_call(&self, call: Call, meta: T) -> FutureRpcOutput { - use self::future::Either::{A, B}; + use self::future::Either::{Left, Right}; self.middleware.on_call(call, meta, |call, meta| match call { Call::MethodCall(method) => { @@ -259,10 +274,7 @@ impl> MetaIoHandler { let jsonrpc = method.jsonrpc; let valid_version = self.compatibility.is_version_valid(jsonrpc); - let call_method = |method: &Arc>| { - let method = method.clone(); - futures::lazy(move || method.call(params, meta)) - }; + let call_method = |method: &Arc>| method.call(params, meta); let result = match (valid_version, self.methods.get(&method.method)) { (false, _) => Err(Error::invalid_version()), @@ -275,17 +287,17 @@ impl> MetaIoHandler { }; match result { - Ok(result) => A(Box::new( - result.then(move |result| futures::finished(Some(Output::from(result, id, jsonrpc)))), + Ok(result) => Left(Box::pin( + result.then(move |result| future::ready(Some(Output::from(result, id, jsonrpc)))), ) as _), - Err(err) => B(futures::finished(Some(Output::from(Err(err), id, jsonrpc)))), + Err(err) => Right(future::ready(Some(Output::from(Err(err), id, jsonrpc)))), } } Call::Notification(notification) => { let params = notification.params; let jsonrpc = notification.jsonrpc; if !self.compatibility.is_version_valid(jsonrpc) { - return B(futures::finished(None)); + return Right(future::ready(None)); } match self.methods.get(¬ification.method) { @@ -300,9 +312,9 @@ impl> MetaIoHandler { _ => {} } - B(futures::finished(None)) + Right(future::ready(None)) } - Call::Invalid { id } => B(futures::finished(Some(Output::invalid_request( + Call::Invalid { id } => Right(future::ready(Some(Output::invalid_request( id, self.compatibility.default_version(), )))), @@ -431,6 +443,7 @@ impl IoHandler { /// Handle given request synchronously - will block until response is available. /// If you have any asynchronous methods in your RPC it is much wiser to use /// `handle_request` instead and deal with asynchronous requests in a non-blocking fashion. + #[cfg(feature = "futures-executor")] pub fn handle_request_sync(&self, request: &str) -> Option { self.0.handle_request_sync(request, M::default()) } @@ -475,13 +488,12 @@ fn write_response(response: Response) -> String { mod tests { use super::{Compatibility, IoHandler}; use crate::types::Value; - use futures; #[test] fn test_io_handler() { let mut io = IoHandler::new(); - io.add_method("say_hello", |_| Ok(Value::String("hello".to_string()))); + io.add_method("say_hello", |_| async { Ok(Value::String("hello".to_string())) }); let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"hello","id":1}"#; @@ -493,7 +505,7 @@ mod tests { fn test_io_handler_1dot0() { let mut io = IoHandler::with_compatibility(Compatibility::Both); - io.add_method("say_hello", |_| Ok(Value::String("hello".to_string()))); + io.add_method("say_hello", |_| async { Ok(Value::String("hello".to_string())) }); let request = r#"{"method": "say_hello", "params": [42, 23], "id": 1}"#; let response = r#"{"result":"hello","id":1}"#; @@ -505,7 +517,7 @@ mod tests { fn test_async_io_handler() { let mut io = IoHandler::new(); - io.add_method("say_hello", |_| futures::finished(Value::String("hello".to_string()))); + io.add_method("say_hello", |_| async { Ok(Value::String("hello".to_string())) }); let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"hello","id":1}"#; @@ -544,7 +556,7 @@ mod tests { #[test] fn test_method_alias() { let mut io = IoHandler::new(); - io.add_method("say_hello", |_| Ok(Value::String("hello".to_string()))); + io.add_method("say_hello", |_| async { Ok(Value::String("hello".to_string())) }); io.add_alias("say_hello_alias", "say_hello"); let request = r#"{"jsonrpc": "2.0", "method": "say_hello_alias", "params": [42, 23], "id": 1}"#; @@ -612,8 +624,8 @@ mod tests { struct Test; impl Test { - fn abc(&self, _p: crate::Params) -> Result { - Ok(5.into()) + fn abc(&self, _p: crate::Params) -> crate::BoxFuture> { + Box::pin(async { Ok(5.into()) }) } } diff --git a/core/src/lib.rs b/core/src/lib.rs index 1e0f27fd1..736363c3d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -4,29 +4,34 @@ //! //! ```rust //! use jsonrpc_core::*; -//! use jsonrpc_core::futures::Future; //! //! fn main() { //! let mut io = IoHandler::new(); -//! io.add_method("say_hello", |_| { +//! io.add_sync_method("say_hello", |_| { //! Ok(Value::String("Hello World!".into())) //! }); //! //! let request = r#"{"jsonrpc": "2.0", "method": "say_hello", "params": [42, 23], "id": 1}"#; //! let response = r#"{"jsonrpc":"2.0","result":"Hello World!","id":1}"#; //! -//! assert_eq!(io.handle_request(request).wait().unwrap(), Some(response.to_string())); +//! assert_eq!(io.handle_request_sync(request), Some(response.to_string())); //! } //! ``` #![deny(missing_docs)] +use std::pin::Pin; + #[macro_use] extern crate log; #[macro_use] extern crate serde_derive; +#[cfg(feature = "futures")] pub use futures; +#[cfg(feature = "futures-executor")] +pub use futures_executor; +pub use futures_util; #[doc(hidden)] pub extern crate serde; @@ -40,13 +45,16 @@ pub mod delegates; pub mod middleware; pub mod types; -/// A `Future` trait object. -pub type BoxFuture = Box + Send>; - /// A Result type. -pub type Result = ::std::result::Result; +pub type Result = std::result::Result; -pub use crate::calls::{Metadata, RemoteProcedure, RpcMethod, RpcMethodSimple, RpcNotification, RpcNotificationSimple}; +/// A `Future` trait object. +pub type BoxFuture = Pin + Send>>; + +pub use crate::calls::{ + Metadata, RemoteProcedure, RpcMethod, RpcMethodSimple, RpcMethodSync, RpcNotification, RpcNotificationSimple, + WrapFuture, +}; pub use crate::delegates::IoDelegate; pub use crate::io::{ Compatibility, FutureOutput, FutureResponse, FutureResult, FutureRpcResult, IoHandler, IoHandlerExtension, diff --git a/core/src/middleware.rs b/core/src/middleware.rs index a33ae4add..308a787c4 100644 --- a/core/src/middleware.rs +++ b/core/src/middleware.rs @@ -2,15 +2,17 @@ use crate::calls::Metadata; use crate::types::{Call, Output, Request, Response}; -use futures::{future::Either, Future}; +use futures_util::future::Either; +use std::future::Future; +use std::pin::Pin; /// RPC middleware pub trait Middleware: Send + Sync + 'static { /// A returned request future. - type Future: Future, Error = ()> + Send + 'static; + type Future: Future> + Send + 'static; /// A returned call future. - type CallFuture: Future, Error = ()> + Send + 'static; + type CallFuture: Future> + Send + 'static; /// Method invoked on each request. /// Allows you to either respond directly (without executing RPC call) @@ -18,9 +20,9 @@ pub trait Middleware: Send + Sync + 'static { fn on_request(&self, request: Request, meta: M, next: F) -> Either where F: Fn(Request, M) -> X + Send + Sync, - X: Future, Error = ()> + Send + 'static, + X: Future> + Send + 'static, { - Either::B(next(request, meta)) + Either::Right(next(request, meta)) } /// Method invoked on each call inside a request. @@ -29,16 +31,16 @@ pub trait Middleware: Send + Sync + 'static { fn on_call(&self, call: Call, meta: M, next: F) -> Either where F: Fn(Call, M) -> X + Send + Sync, - X: Future, Error = ()> + Send + 'static, + X: Future> + Send + 'static, { - Either::B(next(call, meta)) + Either::Right(next(call, meta)) } } /// Dummy future used as a noop result of middleware. -pub type NoopFuture = Box, Error = ()> + Send>; +pub type NoopFuture = Pin> + Send>>; /// Dummy future used as a noop call result of middleware. -pub type NoopCallFuture = Box, Error = ()> + Send>; +pub type NoopCallFuture = Pin> + Send>>; /// No-op middleware implementation #[derive(Clone, Debug, Default)] @@ -55,7 +57,7 @@ impl, B: Middleware> Middleware for (A, B) { fn on_request(&self, request: Request, meta: M, process: F) -> Either where F: Fn(Request, M) -> X + Send + Sync, - X: Future, Error = ()> + Send + 'static, + X: Future> + Send + 'static, { repack(self.0.on_request(request, meta, |request, meta| { self.1.on_request(request, meta, &process) @@ -65,7 +67,7 @@ impl, B: Middleware> Middleware for (A, B) { fn on_call(&self, call: Call, meta: M, process: F) -> Either where F: Fn(Call, M) -> X + Send + Sync, - X: Future, Error = ()> + Send + 'static, + X: Future> + Send + 'static, { repack( self.0 @@ -81,7 +83,7 @@ impl, B: Middleware, C: Middleware> Middlewa fn on_request(&self, request: Request, meta: M, process: F) -> Either where F: Fn(Request, M) -> X + Send + Sync, - X: Future, Error = ()> + Send + 'static, + X: Future> + Send + 'static, { repack(self.0.on_request(request, meta, |request, meta| { repack(self.1.on_request(request, meta, |request, meta| { @@ -93,7 +95,7 @@ impl, B: Middleware, C: Middleware> Middlewa fn on_call(&self, call: Call, meta: M, process: F) -> Either where F: Fn(Call, M) -> X + Send + Sync, - X: Future, Error = ()> + Send + 'static, + X: Future> + Send + 'static, { repack(self.0.on_call(call, meta, |call, meta| { repack( @@ -113,7 +115,7 @@ impl, B: Middleware, C: Middleware, D: Middl fn on_request(&self, request: Request, meta: M, process: F) -> Either where F: Fn(Request, M) -> X + Send + Sync, - X: Future, Error = ()> + Send + 'static, + X: Future> + Send + 'static, { repack(self.0.on_request(request, meta, |request, meta| { repack(self.1.on_request(request, meta, |request, meta| { @@ -127,7 +129,7 @@ impl, B: Middleware, C: Middleware, D: Middl fn on_call(&self, call: Call, meta: M, process: F) -> Either where F: Fn(Call, M) -> X + Send + Sync, - X: Future, Error = ()> + Send + 'static, + X: Future> + Send + 'static, { repack(self.0.on_call(call, meta, |call, meta| { repack(self.1.on_call(call, meta, |call, meta| { @@ -143,8 +145,8 @@ impl, B: Middleware, C: Middleware, D: Middl #[inline(always)] fn repack(result: Either>) -> Either, X> { match result { - Either::A(a) => Either::A(Either::A(a)), - Either::B(Either::A(b)) => Either::A(Either::B(b)), - Either::B(Either::B(x)) => Either::B(x), + Either::Left(a) => Either::Left(Either::Left(a)), + Either::Right(Either::Left(b)) => Either::Left(Either::Right(b)), + Either::Right(Either::Right(x)) => Either::Right(x), } } diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 1d94d78f9..9fd65141e 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -7,7 +7,7 @@ homepage = "https://github.com/paritytech/jsonrpc" license = "MIT" name = "jsonrpc-derive" repository = "https://github.com/paritytech/jsonrpc" -version = "14.2.1" +version = "17.1.0" [lib] proc-macro = true @@ -19,12 +19,11 @@ quote = "1.0.6" proc-macro-crate = "0.1.4" [dev-dependencies] -jsonrpc-core = { version = "14.2", path = "../core" } -jsonrpc-core-client = { version = "14.2", path = "../core-client" } -jsonrpc-pubsub = { version = "14.2", path = "../pubsub" } -jsonrpc-tcp-server = { version = "14.2", path = "../tcp" } -futures = "~0.1.6" +assert_matches = "1.3" +jsonrpc-core = { version = "17.1", path = "../core" } +jsonrpc-core-client = { version = "17.1", path = "../core-client" } +jsonrpc-pubsub = { version = "17.1", path = "../pubsub" } +jsonrpc-tcp-server = { version = "17.1", path = "../tcp" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -tokio = "0.1" trybuild = "1.0" diff --git a/derive/examples/client-local.rs b/derive/examples/client-local.rs new file mode 100644 index 000000000..a0fdf6fe1 --- /dev/null +++ b/derive/examples/client-local.rs @@ -0,0 +1,62 @@ +use jsonrpc_core::{ + futures::{self, FutureExt}, + BoxFuture, IoHandler, Result, +}; +use jsonrpc_core_client::transports::local; +use jsonrpc_derive::rpc; + +/// Rpc trait +#[rpc] +pub trait Rpc { + /// Returns a protocol version + #[rpc(name = "protocolVersion")] + fn protocol_version(&self) -> Result; + + /// Adds two numbers and returns a result + #[rpc(name = "add", alias("callAsyncMetaAlias"))] + fn add(&self, a: u64, b: u64) -> Result; + + /// Performs asynchronous operation + #[rpc(name = "callAsync")] + fn call(&self, a: u64) -> BoxFuture>; +} + +struct RpcImpl; + +impl Rpc for RpcImpl { + fn protocol_version(&self) -> Result { + Ok("version1".into()) + } + + fn add(&self, a: u64, b: u64) -> Result { + Ok(a + b) + } + + fn call(&self, _: u64) -> BoxFuture> { + Box::pin(futures::future::ready(Ok("OK".to_owned()))) + } +} + +fn main() { + futures::executor::block_on(async { + let mut io = IoHandler::new(); + io.extend_with(RpcImpl.to_delegate()); + println!("Starting local server"); + let (client, server) = local::connect(io); + let client = use_client(client).fuse(); + let server = server.fuse(); + + futures::pin_mut!(client); + futures::pin_mut!(server); + + futures::select! { + _server = server => {}, + _client = client => {}, + } + }); +} + +async fn use_client(client: RpcClient) { + let res = client.add(5, 6).await.unwrap(); + println!("5 + 6 = {}", res); +} diff --git a/derive/examples/generic-trait-bounds.rs b/derive/examples/generic-trait-bounds.rs index 552a86a30..6ac09705c 100644 --- a/derive/examples/generic-trait-bounds.rs +++ b/derive/examples/generic-trait-bounds.rs @@ -1,7 +1,6 @@ use serde::{Deserialize, Serialize}; -use jsonrpc_core::futures::future::{self, FutureResult}; -use jsonrpc_core::{Error, IoHandler, IoHandlerExtension, Result}; +use jsonrpc_core::{futures::future, BoxFuture, IoHandler, IoHandlerExtension, Result}; use jsonrpc_derive::rpc; // One is both parameter and a result so requires both Serialize and DeserializeOwned @@ -22,7 +21,7 @@ pub trait Rpc { /// Performs asynchronous operation #[rpc(name = "beFancy")] - fn call(&self, a: One) -> FutureResult<(One, u64), Error>; + fn call(&self, a: One) -> BoxFuture>; } struct RpcImpl; @@ -49,8 +48,8 @@ impl Rpc for RpcImpl { Ok(Out {}) } - fn call(&self, num: InAndOut) -> FutureResult<(InAndOut, u64), Error> { - crate::future::finished((InAndOut { foo: num.foo + 999 }, num.foo)) + fn call(&self, num: InAndOut) -> BoxFuture> { + Box::pin(future::ready(Ok((InAndOut { foo: num.foo + 999 }, num.foo)))) } } diff --git a/derive/examples/generic-trait.rs b/derive/examples/generic-trait.rs index 6cf867b31..8e0972bc8 100644 --- a/derive/examples/generic-trait.rs +++ b/derive/examples/generic-trait.rs @@ -1,7 +1,6 @@ use jsonrpc_core; -use jsonrpc_core::futures::future::{self, FutureResult}; -use jsonrpc_core::{Error, IoHandler, Result}; +use jsonrpc_core::{BoxFuture, IoHandler, Result}; use jsonrpc_derive::rpc; #[rpc] @@ -16,7 +15,7 @@ pub trait Rpc { /// Performs asynchronous operation #[rpc(name = "beFancy")] - fn call(&self, a: One) -> FutureResult<(One, Two), Error>; + fn call(&self, a: One) -> BoxFuture>; } struct RpcImpl; @@ -31,8 +30,8 @@ impl Rpc for RpcImpl { Ok(()) } - fn call(&self, num: u64) -> FutureResult<(u64, String), Error> { - crate::future::finished((num + 999, "hello".into())) + fn call(&self, num: u64) -> BoxFuture> { + Box::pin(jsonrpc_core::futures::future::ready(Ok((num + 999, "hello".into())))) } } diff --git a/derive/examples/meta-macros.rs b/derive/examples/meta-macros.rs index 8d7a64d7f..9ea7ea69e 100644 --- a/derive/examples/meta-macros.rs +++ b/derive/examples/meta-macros.rs @@ -1,8 +1,7 @@ use std::collections::BTreeMap; -use jsonrpc_core::futures::future::FutureResult; -use jsonrpc_core::types::params::Params; -use jsonrpc_core::{futures, Error, MetaIoHandler, Metadata, Result, Value}; +use jsonrpc_core::futures::future; +use jsonrpc_core::{BoxFuture, MetaIoHandler, Metadata, Params, Result, Value}; use jsonrpc_derive::rpc; #[derive(Clone)] @@ -31,11 +30,11 @@ pub trait Rpc { /// Performs an asynchronous operation. #[rpc(name = "callAsync")] - fn call(&self, a: u64) -> FutureResult; + fn call(&self, a: u64) -> BoxFuture>; /// Performs an asynchronous operation with meta. #[rpc(meta, name = "callAsyncMeta", alias("callAsyncMetaAlias"))] - fn call_meta(&self, a: Self::Metadata, b: BTreeMap) -> FutureResult; + fn call_meta(&self, a: Self::Metadata, b: BTreeMap) -> BoxFuture>; /// Handles a notification. #[rpc(name = "notify")] @@ -62,12 +61,12 @@ impl Rpc for RpcImpl { Ok(format!("Got: {:?}", params)) } - fn call(&self, x: u64) -> FutureResult { - futures::finished(format!("OK: {}", x)) + fn call(&self, x: u64) -> BoxFuture> { + Box::pin(future::ready(Ok(format!("OK: {}", x)))) } - fn call_meta(&self, meta: Self::Metadata, map: BTreeMap) -> FutureResult { - futures::finished(format!("From: {}, got: {:?}", meta.0, map)) + fn call_meta(&self, meta: Self::Metadata, map: BTreeMap) -> BoxFuture> { + Box::pin(future::ready(Ok(format!("From: {}, got: {:?}", meta.0, map)))) } fn notify(&self, a: u64) { diff --git a/derive/examples/pubsub-macros.rs b/derive/examples/pubsub-macros.rs index 5346e1068..ec8479a27 100644 --- a/derive/examples/pubsub-macros.rs +++ b/derive/examples/pubsub-macros.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; use std::sync::{atomic, Arc, RwLock}; use std::thread; -use jsonrpc_core::futures::Future; use jsonrpc_core::{Error, ErrorCode, Result}; use jsonrpc_derive::rpc; use jsonrpc_pubsub::typed; @@ -70,7 +69,7 @@ fn main() { { let subscribers = active_subscriptions.read().unwrap(); for sink in subscribers.values() { - let _ = sink.notify(Ok("Hello World!".into())).wait(); + let _ = sink.notify(Ok("Hello World!".into())); } } thread::sleep(::std::time::Duration::from_secs(1)); diff --git a/derive/examples/std.rs b/derive/examples/std.rs index 401bb05c5..3c2c1e640 100644 --- a/derive/examples/std.rs +++ b/derive/examples/std.rs @@ -1,7 +1,7 @@ //! A simple example #![deny(missing_docs)] -use jsonrpc_core::futures::future::{self, Future, FutureResult}; -use jsonrpc_core::{Error, IoHandler, Result}; +use jsonrpc_core::futures::{self, future, TryFutureExt}; +use jsonrpc_core::{BoxFuture, IoHandler, Result}; use jsonrpc_core_client::transports::local; use jsonrpc_derive::rpc; @@ -18,7 +18,7 @@ pub trait Rpc { /// Performs asynchronous operation. #[rpc(name = "callAsync")] - fn call(&self, a: u64) -> FutureResult; + fn call(&self, a: u64) -> BoxFuture>; /// Handles a notification. #[rpc(name = "notify")] @@ -36,8 +36,8 @@ impl Rpc for RpcImpl { Ok(a + b) } - fn call(&self, _: u64) -> FutureResult { - future::ok("OK".to_owned()) + fn call(&self, _: u64) -> BoxFuture> { + Box::pin(future::ready(Ok("OK".to_owned()))) } fn notify(&self, a: u64) { @@ -49,9 +49,10 @@ fn main() { let mut io = IoHandler::new(); io.extend_with(RpcImpl.to_delegate()); - let fut = { - let (client, server) = local::connect::(io); - client.add(5, 6).map(|res| println!("5 + 6 = {}", res)).join(server) - }; - fut.wait().unwrap(); + let (client, server) = local::connect::(io); + let fut = client.add(5, 6).map_ok(|res| println!("5 + 6 = {}", res)); + + futures::executor::block_on(async move { futures::join!(fut, server) }) + .0 + .unwrap(); } diff --git a/derive/src/lib.rs b/derive/src/lib.rs index fa8d9fdfe..9f0f50657 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -6,8 +6,8 @@ //! //! ``` //! use jsonrpc_derive::rpc; -//! use jsonrpc_core::{IoHandler, Error, Result}; -//! use jsonrpc_core::futures::future::{self, FutureResult}; +//! use jsonrpc_core::{IoHandler, Result, BoxFuture}; +//! use jsonrpc_core::futures::future; //! //! #[rpc(server)] //! pub trait Rpc { @@ -18,7 +18,7 @@ //! fn add(&self, a: u64, b: u64) -> Result; //! //! #[rpc(name = "callAsync")] -//! fn call(&self, a: u64) -> FutureResult; +//! fn call(&self, a: u64) -> BoxFuture>; //! } //! //! struct RpcImpl; @@ -31,8 +31,8 @@ //! Ok(a + b) //! } //! -//! fn call(&self, _: u64) -> FutureResult { -//! future::ok("OK".to_owned()).into() +//! fn call(&self, _: u64) -> BoxFuture> { +//! Box::pin(future::ready(Ok("OK".to_owned()).into())) //! } //! } //! @@ -56,7 +56,6 @@ //! use std::collections::HashMap; //! //! use jsonrpc_core::{Error, ErrorCode, Result}; -//! use jsonrpc_core::futures::Future; //! use jsonrpc_derive::rpc; //! use jsonrpc_pubsub::{Session, PubSubHandler, SubscriptionId, typed::{Subscriber, Sink}}; //! @@ -121,15 +120,30 @@ //! } //! } //! -//! # fn main() {} +//! fn main() { +//! let mut io = jsonrpc_core::MetaIoHandler::default(); +//! io.extend_with(RpcImpl::default().to_delegate()); +//! +//! let server_builder = jsonrpc_tcp_server::ServerBuilder::with_meta_extractor( +//! io, +//! |request: &jsonrpc_tcp_server::RequestContext| Arc::new(Session::new(request.sender.clone())) +//! ); +//! let server = server_builder +//! .start(&"127.0.0.1:3030".parse().unwrap()) +//! .expect("Unable to start TCP server"); +//! +//! // The server spawns a separate thread. Dropping the `server` handle causes it to close. +//! // Uncomment the line below to keep the server running in your example. +//! // server.wait(); +//! } //! ``` //! //! Client Example //! //! ``` //! use jsonrpc_core_client::transports::local; -//! use jsonrpc_core::futures::future::{self, Future, FutureResult}; -//! use jsonrpc_core::{Error, IoHandler, Result}; +//! use jsonrpc_core::futures::{self, future}; +//! use jsonrpc_core::{IoHandler, Result, BoxFuture}; //! use jsonrpc_derive::rpc; //! //! /// Rpc trait @@ -145,7 +159,7 @@ //! //! /// Performs asynchronous operation //! #[rpc(name = "callAsync")] -//! fn call(&self, a: u64) -> FutureResult; +//! fn call(&self, a: u64) -> BoxFuture>; //! } //! //! struct RpcImpl; @@ -159,20 +173,23 @@ //! Ok(a + b) //! } //! -//! fn call(&self, _: u64) -> FutureResult { -//! future::ok("OK".to_owned()) +//! fn call(&self, _: u64) -> BoxFuture> { +//! Box::pin(future::ready(Ok("OK".to_owned()))) //! } //! } //! //! fn main() { +//! let exec = futures::executor::ThreadPool::new().unwrap(); +//! exec.spawn_ok(run()) +//! } +//! async fn run() { //! let mut io = IoHandler::new(); //! io.extend_with(RpcImpl.to_delegate()); //! -//! let fut = { -//! let (client, server) = local::connect::(io); -//! client.add(5, 6).map(|res| println!("5 + 6 = {}", res)).join(server) -//! }; -//! fut.wait().unwrap(); +//! let (client, server) = local::connect::(io); +//! let res = client.add(5, 6).await.unwrap(); +//! println!("5 + 6 = {}", res); +//! server.await.unwrap() //! } //! //! ``` diff --git a/derive/src/rpc_trait.rs b/derive/src/rpc_trait.rs index 7550ade4f..a95be556b 100644 --- a/derive/src/rpc_trait.rs +++ b/derive/src/rpc_trait.rs @@ -258,8 +258,10 @@ pub fn rpc_impl(input: syn::Item, options: &DeriveOptions) -> Result Result> { @@ -126,7 +127,7 @@ fn generate_client_methods(methods: &[MethodRegistration], options: &DeriveOptio let client_method = syn::parse_quote! { #(#attrs)* - pub fn #name(&self, #args) -> impl Future { + pub fn #name(&self, #args) -> impl Future> { let args = #args_serialized; self.inner.call_method(#rpc_name, #returns_str, args) } @@ -150,7 +151,7 @@ fn generate_client_methods(methods: &[MethodRegistration], options: &DeriveOptio let unsubscribe = unsubscribe.name(); let client_method = syn::parse_quote!( #(#attrs)* - pub fn #name(&self, #args) -> impl Future, Error=RpcError> { + pub fn #name(&self, #args) -> RpcResult> { let args_tuple = (#(#arg_names,)*); self.inner.subscribe(#subscribe, args_tuple, #subscription, #unsubscribe, #returns_str) } @@ -166,7 +167,7 @@ fn generate_client_methods(methods: &[MethodRegistration], options: &DeriveOptio let arg_names = compute_arg_identifiers(&args)?; let client_method = syn::parse_quote! { #(#attrs)* - pub fn #name(&self, #args) -> impl Future { + pub fn #name(&self, #args) -> RpcResult<()> { let args_tuple = (#(#arg_names,)*); self.inner.notify(#rpc_name, args_tuple) } @@ -264,22 +265,38 @@ fn compute_returns(method: &syn::TraitItemMethod, returns: &Option) -> R } fn try_infer_returns(output: &syn::ReturnType) -> Option { + let extract_path_segments = |ty: &syn::Type| match ty { + syn::Type::Path(syn::TypePath { + path: syn::Path { segments, .. }, + .. + }) => Some(segments.clone()), + _ => None, + }; + match output { - syn::ReturnType::Type(_, ty) => match &**ty { - syn::Type::Path(syn::TypePath { - path: syn::Path { segments, .. }, - .. - }) => match &segments[0] { + syn::ReturnType::Type(_, ty) => { + let segments = extract_path_segments(&**ty)?; + let check_segment = |seg: &syn::PathSegment| match seg { syn::PathSegment { ident, arguments, .. } => { - if ident.to_string().ends_with("Result") { - get_first_type_argument(arguments) + let id = ident.to_string(); + let inner = get_first_type_argument(arguments); + if id.ends_with("Result") { + Ok(inner) } else { - None + Err(inner) } } - }, - _ => None, - }, + }; + // Try out first argument (Result) or nested types like: + // BoxFuture> + match check_segment(&segments[0]) { + Ok(returns) => Some(returns?), + Err(inner) => { + let segments = extract_path_segments(&inner?)?; + check_segment(&segments[0]).ok().flatten() + } + } + } _ => None, } } @@ -287,9 +304,9 @@ fn try_infer_returns(output: &syn::ReturnType) -> Option { fn get_first_type_argument(args: &syn::PathArguments) -> Option { match args { syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, .. }) => { - if args.len() > 0 { + if !args.is_empty() { match &args[0] { - syn::GenericArgument::Type(ty) => Some(ty.to_owned()), + syn::GenericArgument::Type(ty) => Some(ty.clone()), _ => None, } } else { diff --git a/derive/src/to_delegate.rs b/derive/src/to_delegate.rs index 8c77dcded..384e92d39 100644 --- a/derive/src/to_delegate.rs +++ b/derive/src/to_delegate.rs @@ -53,9 +53,11 @@ impl MethodRegistration { let unsub_method_ident = unsubscribe.ident(); let unsub_closure = quote! { move |base, id, meta| { - use self::_futures::{Future, IntoFuture}; - Self::#unsub_method_ident(base, meta, id).into_future() - .map(|value| _jsonrpc_core::to_value(value) + use self::_futures::{FutureExt, TryFutureExt}; + self::_jsonrpc_core::WrapFuture::into_future( + Self::#unsub_method_ident(base, meta, id) + ) + .map_ok(|value| _jsonrpc_core::to_value(value) .expect("Expected always-serializable type; qed")) .map_err(Into::into) } @@ -278,15 +280,14 @@ impl RpcMethod { } else { quote! { Ok((#(#tuple_fields, )*)) => { - use self::_futures::{Future, IntoFuture}; - let fut = (method)#method_call - .into_future() - .map(|value| _jsonrpc_core::to_value(value) + use self::_futures::{FutureExt, TryFutureExt}; + let fut = self::_jsonrpc_core::WrapFuture::into_future((method)#method_call) + .map_ok(|value| _jsonrpc_core::to_value(value) .expect("Expected always-serializable type; qed")) .map_err(Into::into as fn(_) -> _jsonrpc_core::Error); - _futures::future::Either::A(fut) + _futures::future::Either::Left(fut) }, - Err(e) => _futures::future::Either::B(_futures::failed(e)), + Err(e) => _futures::future::Either::Right(_futures::future::ready(Err(e))), } }; diff --git a/derive/tests/client.rs b/derive/tests/client.rs index f5eff5ee8..317c3f995 100644 --- a/derive/tests/client.rs +++ b/derive/tests/client.rs @@ -1,4 +1,5 @@ -use futures::prelude::*; +use assert_matches::assert_matches; +use jsonrpc_core::futures::{self, FutureExt, TryFutureExt}; use jsonrpc_core::{IoHandler, Result}; use jsonrpc_core_client::transports::local; use jsonrpc_derive::rpc; @@ -35,16 +36,14 @@ mod client_server { let fut = client .clone() .add(3, 4) - .and_then(move |res| client.notify(res).map(move |_| res)) - .join(rpc_client) - .map(|(res, ())| { - assert_eq!(res, 7); - }) - .map_err(|err| { - eprintln!("{:?}", err); - assert!(false); + .map_ok(move |res| client.notify(res).map(move |_| res)) + .map(|res| { + assert_matches!(res, Ok(Ok(7))); }); - tokio::run(fut); + let exec = futures::executor::ThreadPool::builder().pool_size(1).create().unwrap(); + exec.spawn_ok(async move { + futures::join!(fut, rpc_client).1.unwrap(); + }); } } @@ -64,6 +63,8 @@ mod named_params { #[test] fn client_generates_correct_named_params_payload() { + use jsonrpc_core::futures::{FutureExt, TryFutureExt}; + let expected = json!({ // key names are derived from function parameter names in the trait "number": 3, "string": String::from("test string"), @@ -73,22 +74,18 @@ mod named_params { }); let mut handler = IoHandler::new(); - handler.add_method("call_with_named", |params: Params| Ok(params.into())); + handler.add_sync_method("call_with_named", |params: Params| Ok(params.into())); let (client, rpc_client) = local::connect::(handler); let fut = client .clone() .call_with_named(3, String::from("test string"), json!({"key": ["value"]})) - .and_then(move |res| client.notify(res.clone()).map(move |_| res)) - .join(rpc_client) - .map(move |(res, ())| { - assert_eq!(res, expected); - }) - .map_err(|err| { - eprintln!("{:?}", err); - assert!(false); + .map_ok(move |res| client.notify(res.clone()).map(move |_| res)) + .map(move |res| { + assert_matches!(res, Ok(Ok(x)) if x == expected); }); - tokio::run(fut); + let exec = futures::executor::ThreadPool::builder().pool_size(1).create().unwrap(); + exec.spawn_ok(async move { futures::join!(fut, rpc_client).1.unwrap() }); } } @@ -115,21 +112,17 @@ mod raw_params { }); let mut handler = IoHandler::new(); - handler.add_method("call_raw", |params: Params| Ok(params.into())); + handler.add_sync_method("call_raw", |params: Params| Ok(params.into())); let (client, rpc_client) = local::connect::(handler); let fut = client .clone() .call_raw_single_param(expected.clone()) - .and_then(move |res| client.notify(res.clone()).map(move |_| res)) - .join(rpc_client) - .map(move |(res, ())| { - assert_eq!(res, expected); - }) - .map_err(|err| { - eprintln!("{:?}", err); - assert!(false); + .map_ok(move |res| client.notify(res.clone()).map(move |_| res)) + .map(move |res| { + assert_matches!(res, Ok(Ok(x)) if x == expected); }); - tokio::run(fut); + let exec = futures::executor::ThreadPool::builder().pool_size(1).create().unwrap(); + exec.spawn_ok(async move { futures::join!(fut, rpc_client).1.unwrap() }); } } diff --git a/derive/tests/pubsub-macros.rs b/derive/tests/pubsub-macros.rs index 0b5c88912..71f7615fd 100644 --- a/derive/tests/pubsub-macros.rs +++ b/derive/tests/pubsub-macros.rs @@ -4,7 +4,7 @@ use serde_json; #[macro_use] extern crate jsonrpc_derive; -use jsonrpc_core::futures::sync::mpsc; +use jsonrpc_core::futures::channel::mpsc; use jsonrpc_pubsub::typed::Subscriber; use jsonrpc_pubsub::{PubSubHandler, PubSubMetadata, Session, SubscriptionId}; use std::sync::Arc; @@ -75,7 +75,7 @@ struct Metadata; impl jsonrpc_core::Metadata for Metadata {} impl PubSubMetadata for Metadata { fn session(&self) -> Option> { - let (tx, _rx) = mpsc::channel(1); + let (tx, _rx) = mpsc::unbounded(); Some(Arc::new(Session::new(tx))) } } diff --git a/derive/tests/run-pass/client_only.rs b/derive/tests/run-pass/client_only.rs index ff86ef140..25f7712bb 100644 --- a/derive/tests/run-pass/client_only.rs +++ b/derive/tests/run-pass/client_only.rs @@ -4,7 +4,7 @@ extern crate jsonrpc_core_client; extern crate jsonrpc_derive; use jsonrpc_core::IoHandler; -use jsonrpc_core::futures::future::Future; +use jsonrpc_core::futures::{self, TryFutureExt}; use jsonrpc_core_client::transports::local; #[rpc(client)] @@ -24,9 +24,7 @@ fn main() { let (client, _rpc_client) = local::connect::(handler); client .add(5, 6) - .map(|res| println!("5 + 6 = {}", res)) + .map_ok(|res| println!("5 + 6 = {}", res)) }; - fut - .wait() - .ok(); + let _ = futures::executor::block_on(fut); } diff --git a/derive/tests/run-pass/client_with_generic_trait_bounds.rs b/derive/tests/run-pass/client_with_generic_trait_bounds.rs index 196f189ba..b0e780c19 100644 --- a/derive/tests/run-pass/client_with_generic_trait_bounds.rs +++ b/derive/tests/run-pass/client_with_generic_trait_bounds.rs @@ -1,5 +1,5 @@ -use jsonrpc_core::futures::future::{self, FutureResult}; -use jsonrpc_core::{Error, IoHandler, Result}; +use jsonrpc_core::futures::future; +use jsonrpc_core::{IoHandler, Result, BoxFuture}; use jsonrpc_derive::rpc; use std::collections::BTreeMap; @@ -15,7 +15,7 @@ where /// Performs asynchronous operation #[rpc(name = "beFancy")] - fn call(&self, a: One) -> FutureResult<(One, Two), Error>; + fn call(&self, a: One) -> BoxFuture>; } struct RpcImpl; @@ -26,8 +26,8 @@ impl Rpc for RpcImpl { Ok(Default::default()) } - fn call(&self, num: u64) -> FutureResult<(u64, String), Error> { - crate::future::finished((num + 999, "hello".into())) + fn call(&self, num: u64) -> BoxFuture> { + Box::pin(future::ready(Ok((num + 999, "hello".into())))) } } diff --git a/derive/tests/ui/attr-invalid-meta-list-names.stderr b/derive/tests/ui/attr-invalid-meta-list-names.stderr index 443966e27..b1beb38c4 100644 --- a/derive/tests/ui/attr-invalid-meta-list-names.stderr +++ b/derive/tests/ui/attr-invalid-meta-list-names.stderr @@ -1,8 +1,7 @@ error: Invalid attribute parameter(s): 'Xalias'. Expected 'alias' --> $DIR/attr-invalid-meta-list-names.rs:5:2 | -5 | /// Returns a protocol version - | _____^ +5 | / /// Returns a protocol version 6 | | #[rpc(name = "protocolVersion", Xalias("alias"))] 7 | | fn protocol_version(&self) -> Result; | |_________________________________________________^ diff --git a/derive/tests/ui/attr-invalid-meta-words.stderr b/derive/tests/ui/attr-invalid-meta-words.stderr index 7b5d63d17..b76def12b 100644 --- a/derive/tests/ui/attr-invalid-meta-words.stderr +++ b/derive/tests/ui/attr-invalid-meta-words.stderr @@ -1,8 +1,7 @@ error: Invalid attribute parameter(s): 'Xmeta'. Expected 'meta, raw_params' --> $DIR/attr-invalid-meta-words.rs:5:2 | -5 | /// Returns a protocol version - | _____^ +5 | / /// Returns a protocol version 6 | | #[rpc(name = "protocolVersion", Xmeta)] 7 | | fn protocol_version(&self) -> Result; | |_________________________________________________^ diff --git a/derive/tests/ui/attr-invalid-name-values.stderr b/derive/tests/ui/attr-invalid-name-values.stderr index 6811f0c3e..f308254c1 100644 --- a/derive/tests/ui/attr-invalid-name-values.stderr +++ b/derive/tests/ui/attr-invalid-name-values.stderr @@ -1,8 +1,7 @@ error: Invalid attribute parameter(s): 'Xname'. Expected 'name, returns, params' --> $DIR/attr-invalid-name-values.rs:5:2 | -5 | /// Returns a protocol version - | _____^ +5 | / /// Returns a protocol version 6 | | #[rpc(Xname = "protocolVersion")] 7 | | fn protocol_version(&self) -> Result; | |_________________________________________________^ diff --git a/derive/tests/ui/attr-missing-rpc-name.stderr b/derive/tests/ui/attr-missing-rpc-name.stderr index fca07fc75..d919e31de 100644 --- a/derive/tests/ui/attr-missing-rpc-name.stderr +++ b/derive/tests/ui/attr-missing-rpc-name.stderr @@ -1,8 +1,7 @@ error: rpc attribute should have a name e.g. `name = "method_name"` --> $DIR/attr-missing-rpc-name.rs:5:2 | -5 | /// Returns a protocol version - | _____^ +5 | / /// Returns a protocol version 6 | | #[rpc] 7 | | fn protocol_version(&self) -> Result; | |_________________________________________________^ diff --git a/derive/tests/ui/multiple-rpc-attributes.stderr b/derive/tests/ui/multiple-rpc-attributes.stderr index 05099f7e3..dddb37788 100644 --- a/derive/tests/ui/multiple-rpc-attributes.stderr +++ b/derive/tests/ui/multiple-rpc-attributes.stderr @@ -1,8 +1,7 @@ error: Expected only a single rpc attribute per method --> $DIR/multiple-rpc-attributes.rs:5:2 | -5 | /// Returns a protocol version - | _____^ +5 | / /// Returns a protocol version 6 | | #[rpc(name = "protocolVersion")] 7 | | #[rpc(name = "protocolVersionAgain")] 8 | | fn protocol_version(&self) -> Result; diff --git a/derive/tests/ui/too-many-params.stderr b/derive/tests/ui/too-many-params.stderr index b83347661..3c23fdf72 100644 --- a/derive/tests/ui/too-many-params.stderr +++ b/derive/tests/ui/too-many-params.stderr @@ -1,8 +1,7 @@ error: Maximum supported number of params is 16 --> $DIR/too-many-params.rs:5:2 | -5 | /// Has too many params - | _____^ +5 | / /// Has too many params 6 | | #[rpc(name = "tooManyParams")] 7 | | fn to_many_params( 8 | | &self, diff --git a/http/Cargo.toml b/http/Cargo.toml index 7474084c6..1e6172abc 100644 --- a/http/Cargo.toml +++ b/http/Cargo.toml @@ -8,16 +8,17 @@ keywords = ["jsonrpc", "json-rpc", "json", "rpc", "server"] license = "MIT" name = "jsonrpc-http-server" repository = "https://github.com/paritytech/jsonrpc" -version = "14.2.0" +version = "17.1.0" [dependencies] -hyper = "0.12" -jsonrpc-core = { version = "14.2", path = "../core" } -jsonrpc-server-utils = { version = "14.2", path = "../server-utils" } +futures = "0.3" +hyper = "0.13" +jsonrpc-core = { version = "17.1", path = "../core" } +jsonrpc-server-utils = { version = "17.1", path = "../server-utils" } log = "0.4" net2 = "0.2" +parking_lot = "0.11.0" unicase = "2.0" -parking_lot = "0.10.0" [dev-dependencies] env_logger = "0.7" diff --git a/http/README.md b/http/README.md index 257704042..c80a2e65c 100644 --- a/http/README.md +++ b/http/README.md @@ -9,7 +9,7 @@ Rust http server using JSON-RPC 2.0. ``` [dependencies] -jsonrpc-http-server = "14.2" +jsonrpc-http-server = "15.0" ``` `main.rs` diff --git a/http/examples/http_async.rs b/http/examples/http_async.rs index 44704f5ea..c243bb875 100644 --- a/http/examples/http_async.rs +++ b/http/examples/http_async.rs @@ -4,7 +4,7 @@ use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBui fn main() { let mut io = IoHandler::default(); io.add_method("say_hello", |_params| { - futures::finished(Value::String("hello".to_owned())) + futures::future::ready(Ok(Value::String("hello".to_owned()))) }); let server = ServerBuilder::new(io) diff --git a/http/examples/http_meta.rs b/http/examples/http_meta.rs index b3b40a407..50d412148 100644 --- a/http/examples/http_meta.rs +++ b/http/examples/http_meta.rs @@ -13,13 +13,13 @@ fn main() { io.add_method_with_meta("say_hello", |_params: Params, meta: Meta| { let auth = meta.auth.unwrap_or_else(String::new); - if auth.as_str() == "let-me-in" { + futures::future::ready(if auth.as_str() == "let-me-in" { Ok(Value::String("Hello World!".to_owned())) } else { Ok(Value::String( "Please send a valid Bearer token in Authorization header.".to_owned(), )) - } + }) }); let server = ServerBuilder::new(io) diff --git a/http/examples/http_middleware.rs b/http/examples/http_middleware.rs index 47d03cd06..68629d78c 100644 --- a/http/examples/http_middleware.rs +++ b/http/examples/http_middleware.rs @@ -5,7 +5,7 @@ use jsonrpc_http_server::{hyper, AccessControlAllowOrigin, DomainsValidation, Re fn main() { let mut io = IoHandler::default(); io.add_method("say_hello", |_params| { - futures::finished(Value::String("hello".to_owned())) + futures::future::ready(Ok(Value::String("hello".to_owned()))) }); let server = ServerBuilder::new(io) diff --git a/http/examples/server.rs b/http/examples/server.rs index 2e9ebc5e0..a8bdfce5d 100644 --- a/http/examples/server.rs +++ b/http/examples/server.rs @@ -5,7 +5,7 @@ fn main() { env_logger::init(); let mut io = IoHandler::default(); - io.add_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); + io.add_sync_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); let server = ServerBuilder::new(io) .threads(3) diff --git a/http/src/handler.rs b/http/src/handler.rs index 6f72a345b..0466ee844 100644 --- a/http/src/handler.rs +++ b/http/src/handler.rs @@ -1,14 +1,16 @@ use crate::WeakRpc; +use std::future::Future; +use std::pin::Pin; use std::sync::Arc; +use std::task::{self, Poll}; use std::{fmt, mem, str}; use hyper::header::{self, HeaderMap, HeaderValue}; use hyper::{self, service::Service, Body, Method}; -use crate::jsonrpc::futures::{future, Async, Future, Poll, Stream}; use crate::jsonrpc::serde_json; -use crate::jsonrpc::{self as core, middleware, FutureResult, Metadata, Middleware}; +use crate::jsonrpc::{self as core, middleware, Metadata, Middleware}; use crate::response::Response; use crate::server_utils::cors; @@ -57,13 +59,21 @@ impl> ServerHandler { } } -impl> Service for ServerHandler { - type ReqBody = Body; - type ResBody = Body; +impl> Service> for ServerHandler +where + S::Future: Unpin, + S::CallFuture: Unpin, + M: Unpin, +{ + type Response = hyper::Response; type Error = hyper::Error; type Future = Handler; - fn call(&mut self, request: hyper::Request) -> Self::Future { + fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> task::Poll> { + task::Poll::Ready(Ok(())) + } + + fn call(&mut self, request: hyper::Request) -> Self::Future { let is_host_allowed = utils::is_host_allowed(&request, &self.allowed_hosts); let action = self.middleware.on_request(request); @@ -114,42 +124,36 @@ impl> Service for ServerHandler { pub enum Handler> { Rpc(RpcHandler), Err(Option), - Middleware(Box, Error = hyper::Error> + Send>), + Middleware(Pin>> + Send>>), } -impl> Future for Handler { - type Item = hyper::Response; - type Error = hyper::Error; - - fn poll(&mut self) -> Poll { - match *self { - Handler::Rpc(ref mut handler) => handler.poll(), - Handler::Middleware(ref mut middleware) => middleware.poll(), - Handler::Err(ref mut response) => Ok(Async::Ready( - response - .take() - .expect("Response always Some initialy. Returning `Ready` so will never be polled again; qed") - .into(), - )), +impl> Future for Handler +where + S::Future: Unpin, + S::CallFuture: Unpin, + M: Unpin, +{ + type Output = hyper::Result>; + + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + match Pin::into_inner(self) { + Handler::Rpc(ref mut handler) => Pin::new(handler).poll(cx), + Handler::Middleware(ref mut middleware) => Pin::new(middleware).poll(cx), + Handler::Err(ref mut response) => Poll::Ready(Ok(response + .take() + .expect("Response always Some initialy. Returning `Ready` so will never be polled again; qed") + .into())), } } } -enum RpcPollState -where - F: Future, Error = ()>, - G: Future, Error = ()>, -{ - Ready(RpcHandlerState), - NotReady(RpcHandlerState), +enum RpcPollState { + Ready(RpcHandlerState), + NotReady(RpcHandlerState), } -impl RpcPollState -where - F: Future, Error = ()>, - G: Future, Error = ()>, -{ - fn decompose(self) -> (RpcHandlerState, bool) { +impl RpcPollState { + fn decompose(self) -> (RpcHandlerState, bool) { use self::RpcPollState::*; match self { Ready(handler) => (handler, true), @@ -158,16 +162,7 @@ where } } -type FutureResponse = future::Map< - future::Either, ()>, core::FutureRpcResult>, - fn(Option) -> Response, ->; - -enum RpcHandlerState -where - F: Future, Error = ()>, - G: Future, Error = ()>, -{ +enum RpcHandlerState { ReadingHeaders { request: hyper::Request, cors_domains: CorsDomains, @@ -190,16 +185,12 @@ where metadata: M, }, Writing(Response), - Waiting(FutureResult), - WaitingForResponse(FutureResponse), + Waiting(Pin> + Send>>), + WaitingForResponse(Pin + Send>>), Done, } -impl fmt::Debug for RpcHandlerState -where - F: Future, Error = ()>, - G: Future, Error = ()>, -{ +impl fmt::Debug for RpcHandlerState { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { use self::RpcHandlerState::*; @@ -218,7 +209,7 @@ where pub struct RpcHandler> { jsonrpc_handler: WeakRpc, - state: RpcHandlerState, + state: RpcHandlerState, is_options: bool, cors_allow_origin: cors::AllowCors, cors_allow_headers: cors::AllowCors>, @@ -229,12 +220,18 @@ pub struct RpcHandler> { keep_alive: bool, } -impl> Future for RpcHandler { - type Item = hyper::Response; - type Error = hyper::Error; +impl> Future for RpcHandler +where + S::Future: Unpin, + S::CallFuture: Unpin, + M: Unpin, +{ + type Output = hyper::Result>; + + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + let this = Pin::into_inner(self); - fn poll(&mut self) -> Poll { - let new_state = match mem::replace(&mut self.state, RpcHandlerState::Done) { + let new_state = match mem::replace(&mut this.state, RpcHandlerState::Done) { RpcHandlerState::ReadingHeaders { request, cors_domains, @@ -243,19 +240,19 @@ impl> Future for RpcHandler { keep_alive, } => { // Read cors header - self.cors_allow_origin = utils::cors_allow_origin(&request, &cors_domains); - self.cors_allow_headers = utils::cors_allow_headers(&request, &cors_headers); - self.keep_alive = utils::keep_alive(&request, keep_alive); - self.is_options = *request.method() == Method::OPTIONS; + this.cors_allow_origin = utils::cors_allow_origin(&request, &cors_domains); + this.cors_allow_headers = utils::cors_allow_headers(&request, &cors_headers); + this.keep_alive = utils::keep_alive(&request, keep_alive); + this.is_options = *request.method() == Method::OPTIONS; // Read other headers - RpcPollState::Ready(self.read_headers(request, continue_on_invalid_cors)) + RpcPollState::Ready(this.read_headers(request, continue_on_invalid_cors)) } RpcHandlerState::ReadingBody { body, request, metadata, uri, - } => match self.process_body(body, request, uri, metadata) { + } => match this.process_body(body, request, uri, metadata, cx) { Err(BodyError::Utf8(ref e)) => { let mesg = format!("utf-8 encoding error at byte {} in request body", e.valid_up_to()); let resp = Response::bad_request(mesg); @@ -265,19 +262,18 @@ impl> Future for RpcHandler { let resp = Response::too_large("request body size exceeds allowed maximum"); RpcPollState::Ready(RpcHandlerState::Writing(resp)) } - Err(BodyError::Hyper(e)) => return Err(e), + Err(BodyError::Hyper(e)) => return Poll::Ready(Err(e)), Ok(state) => state, }, - RpcHandlerState::ProcessRest { uri, metadata } => self.process_rest(uri, metadata)?, - RpcHandlerState::ProcessHealth { method, metadata } => self.process_health(method, metadata)?, - RpcHandlerState::WaitingForResponse(mut waiting) => match waiting.poll() { - Ok(Async::Ready(response)) => RpcPollState::Ready(RpcHandlerState::Writing(response)), - Ok(Async::NotReady) => RpcPollState::NotReady(RpcHandlerState::WaitingForResponse(waiting)), - Err(e) => RpcPollState::Ready(RpcHandlerState::Writing(Response::internal_error(format!("{:?}", e)))), + RpcHandlerState::ProcessRest { uri, metadata } => this.process_rest(uri, metadata)?, + RpcHandlerState::ProcessHealth { method, metadata } => this.process_health(method, metadata)?, + RpcHandlerState::WaitingForResponse(mut waiting) => match Pin::new(&mut waiting).poll(cx) { + Poll::Ready(response) => RpcPollState::Ready(RpcHandlerState::Writing(response)), + Poll::Pending => RpcPollState::NotReady(RpcHandlerState::WaitingForResponse(waiting)), }, RpcHandlerState::Waiting(mut waiting) => { - match waiting.poll() { - Ok(Async::Ready(response)) => { + match Pin::new(&mut waiting).poll(cx) { + Poll::Ready(response) => { RpcPollState::Ready(RpcHandlerState::Writing(match response { // Notification, just return empty response. None => Response::ok(String::new()), @@ -285,10 +281,7 @@ impl> Future for RpcHandler { Some(result) => Response::ok(format!("{}\n", result)), })) } - Ok(Async::NotReady) => RpcPollState::NotReady(RpcHandlerState::Waiting(waiting)), - Err(e) => { - RpcPollState::Ready(RpcHandlerState::Writing(Response::internal_error(format!("{:?}", e)))) - } + Poll::Pending => RpcPollState::NotReady(RpcHandlerState::Waiting(waiting)), } } state => RpcPollState::NotReady(state), @@ -298,25 +291,25 @@ impl> Future for RpcHandler { match new_state { RpcHandlerState::Writing(res) => { let mut response: hyper::Response = res.into(); - let cors_allow_origin = mem::replace(&mut self.cors_allow_origin, cors::AllowCors::Invalid); - let cors_allow_headers = mem::replace(&mut self.cors_allow_headers, cors::AllowCors::Invalid); + let cors_allow_origin = mem::replace(&mut this.cors_allow_origin, cors::AllowCors::Invalid); + let cors_allow_headers = mem::replace(&mut this.cors_allow_headers, cors::AllowCors::Invalid); Self::set_response_headers( response.headers_mut(), - self.is_options, - self.cors_max_age, + this.is_options, + this.cors_max_age, cors_allow_origin.into(), cors_allow_headers.into(), - self.keep_alive, + this.keep_alive, ); - Ok(Async::Ready(response)) + Poll::Ready(Ok(response)) } state => { - self.state = state; + this.state = state; if is_ready { - self.poll() + Pin::new(this).poll(cx) } else { - Ok(Async::NotReady) + Poll::Pending } } } @@ -337,12 +330,12 @@ impl From for BodyError { } } -impl> RpcHandler { - fn read_headers( - &self, - request: hyper::Request, - continue_on_invalid_cors: bool, - ) -> RpcHandlerState { +impl> RpcHandler +where + S::Future: Unpin, + S::CallFuture: Unpin, +{ + fn read_headers(&self, request: hyper::Request, continue_on_invalid_cors: bool) -> RpcHandlerState { if self.cors_allow_origin == cors::AllowCors::Invalid && !continue_on_invalid_cors { return RpcHandlerState::Writing(Response::invalid_allow_origin()); } @@ -401,11 +394,7 @@ impl> RpcHandler { } } - fn process_health( - &self, - method: String, - metadata: M, - ) -> Result, hyper::Error> { + fn process_health(&self, method: String, metadata: M) -> Result, hyper::Error> { use self::core::types::{Call, Failure, Id, MethodCall, Output, Params, Request, Success, Version}; // Create a request @@ -421,28 +410,26 @@ impl> RpcHandler { None => return Ok(RpcPollState::Ready(RpcHandlerState::Writing(Response::closing()))), }; - Ok(RpcPollState::Ready(RpcHandlerState::WaitingForResponse( - future::Either::B(response).map(|res| match res { - Some(core::Response::Single(Output::Success(Success { result, .. }))) => { - let result = serde_json::to_string(&result).expect("Serialization of result is infallible;qed"); + Ok(RpcPollState::Ready(RpcHandlerState::WaitingForResponse(Box::pin( + async { + match response.await { + Some(core::Response::Single(Output::Success(Success { result, .. }))) => { + let result = serde_json::to_string(&result).expect("Serialization of result is infallible;qed"); - Response::ok(result) - } - Some(core::Response::Single(Output::Failure(Failure { error, .. }))) => { - let result = serde_json::to_string(&error).expect("Serialization of error is infallible;qed"); + Response::ok(result) + } + Some(core::Response::Single(Output::Failure(Failure { error, .. }))) => { + let result = serde_json::to_string(&error).expect("Serialization of error is infallible;qed"); - Response::service_unavailable(result) + Response::service_unavailable(result) + } + e => Response::internal_error(format!("Invalid response for health request: {:?}", e)), } - e => Response::internal_error(format!("Invalid response for health request: {:?}", e)), - }), - ))) + }, + )))) } - fn process_rest( - &self, - uri: hyper::Uri, - metadata: M, - ) -> Result, hyper::Error> { + fn process_rest(&self, uri: hyper::Uri, metadata: M) -> Result, hyper::Error> { use self::core::types::{Call, Id, MethodCall, Params, Request, Value, Version}; // skip the initial / @@ -471,11 +458,11 @@ impl> RpcHandler { None => return Ok(RpcPollState::Ready(RpcHandlerState::Writing(Response::closing()))), }; - Ok(RpcPollState::Ready(RpcHandlerState::Waiting( - future::Either::B(response).map(|res| { - res.map(|x| serde_json::to_string(&x).expect("Serialization of response is infallible;qed")) - }), - ))) + Ok(RpcPollState::Ready(RpcHandlerState::Waiting(Box::pin(async { + response + .await + .map(|x| serde_json::to_string(&x).expect("Serialization of response is infallible;qed")) + })))) } fn process_body( @@ -484,10 +471,14 @@ impl> RpcHandler { mut request: Vec, uri: Option, metadata: M, - ) -> Result, BodyError> { + cx: &mut task::Context<'_>, + ) -> Result, BodyError> { + use futures::Stream; + loop { - match body.poll()? { - Async::Ready(Some(chunk)) => { + let pinned_body = Pin::new(&mut body); + match pinned_body.poll_next(cx)? { + Poll::Ready(Some(chunk)) => { if request .len() .checked_add(chunk.len()) @@ -498,7 +489,7 @@ impl> RpcHandler { } request.extend_from_slice(&*chunk) } - Async::Ready(None) => { + Poll::Ready(None) => { if let (Some(uri), true) = (uri, request.is_empty()) { return Ok(RpcPollState::Ready(RpcHandlerState::ProcessRest { uri, metadata })); } @@ -517,9 +508,9 @@ impl> RpcHandler { }; // Content is ready - return Ok(RpcPollState::Ready(RpcHandlerState::Waiting(response))); + return Ok(RpcPollState::Ready(RpcHandlerState::Waiting(Box::pin(response)))); } - Async::NotReady => { + Poll::Pending => { return Ok(RpcPollState::NotReady(RpcHandlerState::ReadingBody { body, request, diff --git a/http/src/lib.rs b/http/src/lib.rs index 7cc40bedf..a229ae509 100644 --- a/http/src/lib.rs +++ b/http/src/lib.rs @@ -6,7 +6,7 @@ //! //! fn main() { //! let mut io = IoHandler::new(); -//! io.add_method("say_hello", |_: Params| { +//! io.add_sync_method("say_hello", |_: Params| { //! Ok(Value::String("hello".to_string())) //! }); //! @@ -35,24 +35,27 @@ mod response; mod tests; mod utils; +use std::convert::Infallible; +use std::future::Future; use std::io; use std::net::SocketAddr; +use std::pin::Pin; use std::sync::{mpsc, Arc, Weak}; use std::thread; use parking_lot::Mutex; -use crate::jsonrpc::futures::sync::oneshot; -use crate::jsonrpc::futures::{self, Future, Stream}; use crate::jsonrpc::MetaIoHandler; use crate::server_utils::reactor::{Executor, UninitializedExecutor}; -use hyper::{server, Body}; +use futures::{channel::oneshot, future}; +use hyper::Body; use jsonrpc_core as jsonrpc; pub use crate::handler::ServerHandler; pub use crate::response::Response; pub use crate::server_utils::cors::{self, AccessControlAllowOrigin, AllowCors, Origin}; pub use crate::server_utils::hosts::{DomainsValidation, Host}; +pub use crate::server_utils::reactor::TaskExecutor; pub use crate::server_utils::{tokio, SuspendableStream}; pub use crate::utils::{cors_allow_headers, cors_allow_origin, is_host_allowed}; @@ -71,7 +74,7 @@ pub enum RequestMiddlewareAction { /// Should standard hosts validation be performed? should_validate_hosts: bool, /// a future for server response - response: Box, Error = hyper::Error> + Send>, + response: Pin>> + Send>>, }, } @@ -79,7 +82,7 @@ impl From for RequestMiddlewareAction { fn from(o: Response) -> Self { RequestMiddlewareAction::Respond { should_validate_hosts: true, - response: Box::new(futures::future::ok(o.into())), + response: Box::pin(async { Ok(o.into()) }), } } } @@ -88,7 +91,7 @@ impl From> for RequestMiddlewareAction { fn from(response: hyper::Response) -> Self { RequestMiddlewareAction::Respond { should_validate_hosts: true, - response: Box::new(futures::future::ok(response)), + response: Box::pin(async { Ok(response) }), } } } @@ -247,7 +250,12 @@ pub struct ServerBuilder = max_request_body_size: usize, } -impl> ServerBuilder { +impl> ServerBuilder +where + S::Future: Unpin, + S::CallFuture: Unpin, + M: Unpin, +{ /// Creates new `ServerBuilder` for given `IoHandler`. /// /// By default: @@ -261,7 +269,12 @@ impl> ServerBuilder> ServerBuilder { +impl> ServerBuilder +where + S::Future: Unpin, + S::CallFuture: Unpin, + M: Unpin, +{ /// Creates new `ServerBuilder` for given `IoHandler`. /// /// By default: @@ -292,7 +305,7 @@ impl> ServerBuilder { /// Utilize existing event loop executor to poll RPC results. /// /// Applies only to 1 of the threads. Other threads will spawn their own Event Loops. - pub fn event_loop_executor(mut self, executor: tokio::runtime::TaskExecutor) -> Self { + pub fn event_loop_executor(mut self, executor: TaskExecutor) -> Self { self.executor = UninitializedExecutor::Shared(executor); self } @@ -511,7 +524,7 @@ fn serve>( mpsc::Sender>, oneshot::Sender<()>, ), - executor: tokio::runtime::TaskExecutor, + executor: TaskExecutor, addr: SocketAddr, cors_domains: CorsDomains, cors_max_age: Option, @@ -524,11 +537,13 @@ fn serve>( keep_alive: bool, reuse_port: bool, max_request_body_size: usize, -) { +) where + S::Future: Unpin, + S::CallFuture: Unpin, + M: Unpin, +{ let (shutdown_signal, local_addr_tx, done_tx) = signals; - executor.spawn({ - let handle = tokio::reactor::Handle::default(); - + executor.spawn(async move { let bind = move || { let listener = match addr { SocketAddr::V4(_) => net2::TcpBuilder::new_v4()?, @@ -538,26 +553,37 @@ fn serve>( listener.reuse_address(true)?; listener.bind(&addr)?; let listener = listener.listen(1024)?; - let listener = tokio::net::TcpListener::from_std(listener, &handle)?; + let local_addr = listener.local_addr()?; + + // NOTE: Future-proof by explicitly setting the listener socket to + // non-blocking mode of operation (future Tokio/Hyper versions + // require for the callers to do that manually) + listener.set_nonblocking(true)?; + // HACK: See below. + #[cfg(windows)] + let raw_socket = std::os::windows::io::AsRawSocket::as_raw_socket(&listener); + #[cfg(not(windows))] + let raw_socket = (); + + let server_builder = + hyper::Server::from_tcp(listener).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; // Add current host to allowed headers. // NOTE: we need to use `l.local_addr()` instead of `addr` // it might be different! - let local_addr = listener.local_addr()?; - - Ok((listener, local_addr)) + Ok((server_builder, local_addr, raw_socket)) }; let bind_result = match bind() { - Ok((listener, local_addr)) => { + Ok((server_builder, local_addr, raw_socket)) => { // Send local address match local_addr_tx.send(Ok(local_addr)) { - Ok(_) => futures::future::ok((listener, local_addr)), + Ok(_) => Ok((server_builder, local_addr, raw_socket)), Err(_) => { warn!( "Thread {:?} unable to reach receiver, closing server", thread::current().name() ); - futures::future::err(()) + Err(()) } } } @@ -565,54 +591,55 @@ fn serve>( // Send error let _send_result = local_addr_tx.send(Err(err)); - futures::future::err(()) + Err(()) } }; - bind_result - .and_then(move |(listener, local_addr)| { - let allowed_hosts = server_utils::hosts::update(allowed_hosts, &local_addr); - - let mut http = server::conn::Http::new(); - http.keep_alive(keep_alive); - let tcp_stream = SuspendableStream::new(listener.incoming()); - - tcp_stream - .map(move |socket| { - let service = ServerHandler::new( - jsonrpc_handler.downgrade(), - cors_domains.clone(), - cors_max_age, - allowed_headers.clone(), - allowed_hosts.clone(), - request_middleware.clone(), - rest_api, - health_api.clone(), - max_request_body_size, - keep_alive, - ); + let (server_builder, local_addr, _raw_socket) = bind_result?; + + let allowed_hosts = server_utils::hosts::update(allowed_hosts, &local_addr); + + let server_builder = server_builder + .http1_keepalive(keep_alive) + .tcp_nodelay(true) + // Explicitly attempt to recover from accept errors (e.g. too many + // files opened) instead of erroring out the entire server. + .tcp_sleep_on_accept_errors(true); + + let service_fn = hyper::service::make_service_fn(move |_addr_stream| { + let service = ServerHandler::new( + jsonrpc_handler.downgrade(), + cors_domains.clone(), + cors_max_age, + allowed_headers.clone(), + allowed_hosts.clone(), + request_middleware.clone(), + rest_api, + health_api.clone(), + max_request_body_size, + keep_alive, + ); + async { Ok::<_, Infallible>(service) } + }); + + let server = server_builder.serve(service_fn).with_graceful_shutdown(async { + if let Err(err) = shutdown_signal.await { + debug!("Shutdown signaller dropped, closing server: {:?}", err); + } + }); - tokio::spawn( - http.serve_connection(socket, service) - .map_err(|e| error!("Error serving connection: {:?}", e)) - .then(|_| Ok(())), - ) - }) - .for_each(|_| Ok(())) - .map_err(|e| { - warn!("Incoming streams error, closing sever: {:?}", e); - }) - .select(shutdown_signal.map_err(|e| { - debug!("Shutdown signaller dropped, closing server: {:?}", e); - })) - .map_err(|_| ()) - }) - .and_then(|(_, server)| { - // We drop the server first to prevent a situation where main thread terminates - // before the server is properly dropped (see #504 for more details) - drop(server); - done_tx.send(()) - }) + if let Err(err) = server.await { + error!("Error running HTTP server: {:?}", err); + } + + // FIXME: Work around TCP listener socket not being properly closed + // in mio v0.6. This runs the std::net::TcpListener's destructor, + // which closes the underlying OS socket. + // Remove this once we migrate to Tokio 1.0. + #[cfg(windows)] + let _: std::net::TcpListener = unsafe { std::os::windows::io::FromRawSocket::from_raw_socket(_raw_socket) }; + + done_tx.send(()) }); } @@ -643,8 +670,9 @@ impl CloseHandle { pub fn close(self) { if let Some(executors) = self.0.lock().take() { for (executor, closer) in executors { - executor.close(); + // First send shutdown signal so we can proceed with underlying select let _ = closer.send(()); + executor.close(); } } } @@ -681,9 +709,9 @@ impl Server { fn wait_internal(&mut self) { if let Some(receivers) = self.done.take() { - for receiver in receivers { - let _ = receiver.wait(); - } + // NOTE: Gracefully handle the case where we may wait on a *nested* + // local task pool (for now, wait on a dedicated, spawned thread) + let _ = std::thread::spawn(move || futures::executor::block_on(future::try_join_all(receivers))).join(); } } } diff --git a/http/src/tests.rs b/http/src/tests.rs index 31664a6bc..302642599 100644 --- a/http/src/tests.rs +++ b/http/src/tests.rs @@ -6,7 +6,7 @@ use std::net::TcpStream; use std::str::Lines; use std::time::Duration; -use self::jsonrpc_core::futures::{self, Future}; +use self::jsonrpc_core::futures; use super::*; fn serve_hosts(hosts: Vec) -> Server { @@ -38,7 +38,7 @@ fn serve ServerBuilder>(alter: F) -> Server { fn serve_allow_headers(cors_allow_headers: cors::AccessControlAllowHeaders) -> Server { let mut io = IoHandler::default(); - io.add_method("hello", |params: Params| match params.parse::<(u64,)>() { + io.add_sync_method("hello", |params: Params| match params.parse::<(u64,)>() { Ok((num,)) => Ok(Value::String(format!("world: {}", num))), _ => Ok(Value::String("world".into())), }); @@ -54,16 +54,17 @@ fn serve_allow_headers(cors_allow_headers: cors::AccessControlAllowHeaders) -> S fn io() -> IoHandler { let mut io = IoHandler::default(); - io.add_method("hello", |params: Params| match params.parse::<(u64,)>() { + io.add_sync_method("hello", |params: Params| match params.parse::<(u64,)>() { Ok((num,)) => Ok(Value::String(format!("world: {}", num))), _ => Ok(Value::String("world".into())), }); - io.add_method("fail", |_: Params| Err(Error::new(ErrorCode::ServerError(-34)))); + io.add_sync_method("fail", |_: Params| Err(Error::new(ErrorCode::ServerError(-34)))); io.add_method("hello_async", |_params: Params| { - futures::finished(Value::String("world".into())) + futures::future::ready(Ok(Value::String("world".into()))) }); io.add_method("hello_async2", |_params: Params| { - let (c, p) = futures::oneshot(); + use futures::TryFutureExt; + let (c, p) = futures::channel::oneshot::channel(); thread::spawn(move || { thread::sleep(Duration::from_millis(10)); c.send(Value::String("world".into())).unwrap(); @@ -1503,7 +1504,7 @@ fn should_drop_io_handler_when_server_is_closed() { let my_ref = Arc::new(Mutex::new(5)); let weak = Arc::downgrade(&my_ref); let mut io = IoHandler::default(); - io.add_method("hello", move |_| { + io.add_sync_method("hello", move |_| { Ok(Value::String(format!("{}", my_ref.lock().unwrap()))) }); let server = ServerBuilder::new(io) diff --git a/ipc/Cargo.toml b/ipc/Cargo.toml index 00ada6ec4..0eef09da3 100644 --- a/ipc/Cargo.toml +++ b/ipc/Cargo.toml @@ -7,22 +7,23 @@ homepage = "https://github.com/paritytech/jsonrpc" license = "MIT" name = "jsonrpc-ipc-server" repository = "https://github.com/paritytech/jsonrpc" -version = "14.2.0" +version = "17.1.0" [dependencies] +futures = "0.3" log = "0.4" -tokio-service = "0.1" -jsonrpc-core = { version = "14.2", path = "../core" } -jsonrpc-server-utils = { version = "14.2", path = "../server-utils" } -parity-tokio-ipc = "0.4" -parking_lot = "0.10.0" +tower-service = "0.3" +jsonrpc-core = { version = "17.1", path = "../core" } +jsonrpc-server-utils = { version = "17.1", path = "../server-utils", default-features = false } +parity-tokio-ipc = "0.8" +parking_lot = "0.11.0" [dev-dependencies] env_logger = "0.7" lazy_static = "1.0" [target.'cfg(not(windows))'.dev-dependencies] -tokio-uds = "0.2" +tokio = { version = "0.2", default-features = false, features = ["uds", "time", "rt-threaded", "io-driver"] } [badges] travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} diff --git a/ipc/README.md b/ipc/README.md index 60561eb77..b709f8071 100644 --- a/ipc/README.md +++ b/ipc/README.md @@ -9,7 +9,7 @@ IPC server (Windows & Linux) for JSON-RPC 2.0. ``` [dependencies] -jsonrpc-ipc-server = "14.2" +jsonrpc-ipc-server = "15.0" ``` `main.rs` diff --git a/ipc/examples/ipc.rs b/ipc/examples/ipc.rs index 5a94bce0f..483794887 100644 --- a/ipc/examples/ipc.rs +++ b/ipc/examples/ipc.rs @@ -4,7 +4,7 @@ use jsonrpc_ipc_server::jsonrpc_core::*; fn main() { let mut io = MetaIoHandler::<()>::default(); - io.add_method("say_hello", |_params| Ok(Value::String("hello".to_string()))); + io.add_sync_method("say_hello", |_params| Ok(Value::String("hello".to_string()))); let _server = jsonrpc_ipc_server::ServerBuilder::new(io) .start("/tmp/parity-example.ipc") .expect("Server should start ok"); diff --git a/ipc/src/meta.rs b/ipc/src/meta.rs index 8eff47a0e..497eaf086 100644 --- a/ipc/src/meta.rs +++ b/ipc/src/meta.rs @@ -1,4 +1,6 @@ -use crate::jsonrpc::futures::sync::mpsc; +use std::path::Path; + +use crate::jsonrpc::futures::channel::mpsc; use crate::jsonrpc::Metadata; use crate::server_utils::session; @@ -7,9 +9,9 @@ pub struct RequestContext<'a> { /// Session ID pub session_id: session::SessionId, /// Remote UDS endpoint - pub endpoint_addr: &'a ::parity_tokio_ipc::RemoteId, + pub endpoint_addr: &'a Path, /// Direct pipe sender - pub sender: mpsc::Sender, + pub sender: mpsc::UnboundedSender, } /// Metadata extractor (per session) diff --git a/ipc/src/select_with_weak.rs b/ipc/src/select_with_weak.rs index 7bfec7c7c..43c90b783 100644 --- a/ipc/src/select_with_weak.rs +++ b/ipc/src/select_with_weak.rs @@ -1,10 +1,13 @@ -use crate::jsonrpc::futures::stream::{Fuse, Stream}; -use crate::jsonrpc::futures::{Async, Poll}; +use std::pin::Pin; +use std::task::Context; +use std::task::Poll; + +use futures::stream::{Fuse, Stream}; pub trait SelectWithWeakExt: Stream { fn select_with_weak(self, other: S) -> SelectWithWeak where - S: Stream, + S: Stream, Self: Sized; } @@ -14,7 +17,7 @@ where { fn select_with_weak(self, other: S) -> SelectWithWeak where - S: Stream, + S: Stream, Self: Sized, { new(self, other) @@ -39,8 +42,9 @@ pub struct SelectWithWeak { fn new(stream1: S1, stream2: S2) -> SelectWithWeak where S1: Stream, - S2: Stream, + S2: Stream, { + use futures::StreamExt; SelectWithWeak { strong: stream1.fuse(), weak: stream2.fuse(), @@ -50,36 +54,36 @@ where impl Stream for SelectWithWeak where - S1: Stream, - S2: Stream, + S1: Stream + Unpin, + S2: Stream + Unpin, { type Item = S1::Item; - type Error = S1::Error; - fn poll(&mut self) -> Poll, S1::Error> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = Pin::into_inner(self); let mut checked_strong = false; loop { - if self.use_strong { - match self.strong.poll()? { - Async::Ready(Some(item)) => { - self.use_strong = false; - return Ok(Some(item).into()); + if this.use_strong { + match Pin::new(&mut this.strong).poll_next(cx) { + Poll::Ready(Some(item)) => { + this.use_strong = false; + return Poll::Ready(Some(item)); } - Async::Ready(None) => return Ok(None.into()), - Async::NotReady => { + Poll::Ready(None) => return Poll::Ready(None), + Poll::Pending => { if !checked_strong { - self.use_strong = false; + this.use_strong = false; } else { - return Ok(Async::NotReady); + return Poll::Pending; } } } checked_strong = true; } else { - self.use_strong = true; - match self.weak.poll()? { - Async::Ready(Some(item)) => return Ok(Some(item).into()), - Async::Ready(None) | Async::NotReady => (), + this.use_strong = true; + match Pin::new(&mut this.weak).poll_next(cx) { + Poll::Ready(Some(item)) => return Poll::Ready(Some(item)), + Poll::Ready(None) | Poll::Pending => (), } } } diff --git a/ipc/src/server.rs b/ipc/src/server.rs index 933ad8648..107150304 100644 --- a/ipc/src/server.rs +++ b/ipc/src/server.rs @@ -1,20 +1,20 @@ +use std::future::Future; +use std::pin::Pin; use std::sync::Arc; +use std::task::{Context, Poll}; -use crate::jsonrpc::futures::sync::{mpsc, oneshot}; -use crate::jsonrpc::futures::{future, Future, Sink, Stream}; -use crate::jsonrpc::{middleware, FutureResult, MetaIoHandler, Metadata, Middleware}; -use tokio_service::{self, Service as TokioService}; - -use crate::server_utils::{ - codecs, reactor, session, - tokio::{reactor::Handle, runtime::TaskExecutor}, - tokio_codec::Framed, -}; -use parking_lot::Mutex; - +use crate::jsonrpc::futures::channel::mpsc; +use crate::jsonrpc::{middleware, MetaIoHandler, Metadata, Middleware}; use crate::meta::{MetaExtractor, NoopExtractor, RequestContext}; use crate::select_with_weak::SelectWithWeakExt; +use futures::channel::oneshot; +use futures::StreamExt; use parity_tokio_ipc::Endpoint; +use parking_lot::Mutex; +use tower_service::Service as _; + +use crate::server_utils::{codecs, reactor, reactor::TaskExecutor, session, tokio_util}; + pub use parity_tokio_ipc::SecurityAttributes; /// IPC server session @@ -30,17 +30,24 @@ impl> Service { } } -impl> tokio_service::Service for Service { - type Request = String; +impl> tower_service::Service for Service +where + S::Future: Unpin, + S::CallFuture: Unpin, +{ type Response = Option; - type Error = (); - type Future = FutureResult; + type Future = Pin> + Send>>; - fn call(&self, req: Self::Request) -> Self::Future { + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: String) -> Self::Future { + use futures::FutureExt; trace!(target: "ipc", "Received request: {}", req); - self.handler.handle_request(&req, self.meta.clone()) + Box::pin(self.handler.handle_request(&req, self.meta.clone()).map(Ok)) } } @@ -50,14 +57,17 @@ pub struct ServerBuilder = middleware::Noop> meta_extractor: Arc>, session_stats: Option>, executor: reactor::UninitializedExecutor, - reactor: Option, incoming_separator: codecs::Separator, outgoing_separator: codecs::Separator, security_attributes: SecurityAttributes, client_buffer_size: usize, } -impl> ServerBuilder { +impl> ServerBuilder +where + S::Future: Unpin, + S::CallFuture: Unpin, +{ /// Creates new IPC server build given the `IoHandler`. pub fn new(io_handler: T) -> ServerBuilder where @@ -67,7 +77,11 @@ impl> ServerBuilder { } } -impl> ServerBuilder { +impl> ServerBuilder +where + S::Future: Unpin, + S::CallFuture: Unpin, +{ /// Creates new IPC server build given the `IoHandler` and metadata extractor. pub fn with_meta_extractor(io_handler: T, extractor: E) -> ServerBuilder where @@ -79,7 +93,6 @@ impl> ServerBuilder { meta_extractor: Arc::new(extractor), session_stats: None, executor: reactor::UninitializedExecutor::Unspawned, - reactor: None, incoming_separator: codecs::Separator::Empty, outgoing_separator: codecs::Separator::default(), security_attributes: SecurityAttributes::empty(), @@ -93,12 +106,6 @@ impl> ServerBuilder { self } - /// Sets different event loop I/O reactor. - pub fn event_loop_reactor(mut self, reactor: Handle) -> Self { - self.reactor = Some(reactor); - self - } - /// Sets session metadata extractor. pub fn session_meta_extractor(mut self, meta_extractor: X) -> Self where @@ -136,7 +143,6 @@ impl> ServerBuilder { /// Creates a new server from the given endpoint. pub fn start(self, path: &str) -> std::io::Result { let executor = self.executor.initialize()?; - let reactor = self.reactor; let rpc_handler = self.handler; let endpoint_addr = path.to_owned(); let meta_extractor = self.meta_extractor; @@ -144,12 +150,13 @@ impl> ServerBuilder { let incoming_separator = self.incoming_separator; let outgoing_separator = self.outgoing_separator; let (stop_signal, stop_receiver) = oneshot::channel(); - let (start_signal, start_receiver) = oneshot::channel(); - let (wait_signal, wait_receiver) = oneshot::channel(); + // NOTE: These channels are only waited upon in synchronous fashion + let (start_signal, start_receiver) = std::sync::mpsc::channel(); + let (wait_signal, wait_receiver) = std::sync::mpsc::channel(); let security_attributes = self.security_attributes; let client_buffer_size = self.client_buffer_size; - executor.spawn(future::lazy(move || { + let fut = async move { let mut endpoint = Endpoint::new(endpoint_addr); endpoint.set_security_attributes(security_attributes); @@ -160,27 +167,21 @@ impl> ServerBuilder { } } - // Make sure to construct Handle::default() inside Tokio runtime - let reactor = if cfg!(windows) { - #[allow(deprecated)] - reactor.unwrap_or_else(Handle::current) - } else { - reactor.unwrap_or_else(Handle::default) - }; - - let connections = match endpoint.incoming(&reactor) { + let endpoint_addr = endpoint.path().to_owned(); + let connections = match endpoint.incoming() { Ok(connections) => connections, Err(e) => { start_signal .send(Err(e)) .expect("Cannot fail since receiver never dropped before receiving"); - return future::Either::A(future::ok(())); + return; } }; let mut id = 0u64; - let server = connections.map(move |(io_stream, remote_id)| { + use futures::TryStreamExt; + let server = connections.map_ok(move |io_stream| { id = id.wrapping_add(1); let session_id = id; let session_stats = session_stats.clone(); @@ -189,66 +190,58 @@ impl> ServerBuilder { stats.open_session(session_id) } - let (sender, receiver) = mpsc::channel(16); + let (sender, receiver) = mpsc::unbounded(); let meta = meta_extractor.extract(&RequestContext { - endpoint_addr: &remote_id, + endpoint_addr: endpoint_addr.as_ref(), session_id, sender, }); - let service = Service::new(rpc_handler.clone(), meta); - let (writer, reader) = Framed::new( - io_stream, - codecs::StreamCodec::new(incoming_separator.clone(), outgoing_separator.clone()), - ) - .split(); + let mut service = Service::new(rpc_handler.clone(), meta); + let codec = codecs::StreamCodec::new(incoming_separator.clone(), outgoing_separator.clone()); + let framed = tokio_util::codec::Decoder::framed(codec, io_stream); + let (writer, reader) = futures::StreamExt::split(framed); + let responses = reader - .map(move |req| { + .map_ok(move |req| { service .call(req) - .then(|result| match result { - Err(_) => future::ok(None), - Ok(some_result) => future::ok(some_result), - }) - .map_err(|_: ()| std::io::ErrorKind::Other.into()) + // Ignore service errors + .map(|x| Ok(x.ok().flatten())) }) - .buffer_unordered(client_buffer_size) - .filter_map(|x| x) + .try_buffer_unordered(client_buffer_size) + // Filter out previously ignored service errors as `None`s + .try_filter_map(|x| futures::future::ok(x)) // we use `select_with_weak` here, instead of `select`, to close the stream // as soon as the ipc pipe is closed - .select_with_weak(receiver.map_err(|e| { - warn!(target: "ipc", "Notification error: {:?}", e); - std::io::ErrorKind::Other.into() - })); + .select_with_weak(receiver.map(Ok)); - let writer = writer.send_all(responses).then(move |_| { + responses.forward(writer).then(move |_| { trace!(target: "ipc", "Peer: service finished"); if let Some(stats) = session_stats.as_ref() { stats.close_session(session_id) } - Ok(()) - }); - writer + async { Ok(()) } + }) }); start_signal .send(Ok(())) .expect("Cannot fail since receiver never dropped before receiving"); + let stop = stop_receiver.map_err(|_| std::io::ErrorKind::Interrupted); + let stop = Box::pin(stop); - let stop = stop_receiver.map_err(|_| std::io::ErrorKind::Interrupted.into()); - future::Either::B( - server - .buffer_unordered(1024) - .for_each(|_| Ok(())) - .select(stop) - .map(|(_, server)| { - // We drop the server first to prevent a situation where main thread terminates - // before the server is properly dropped (see #504 for more details) - drop(server); - let _ = wait_signal.send(()); - }) - .map_err(|_| ()), - ) - })); + let server = server.try_buffer_unordered(1024).for_each(|_| async {}); + + let result = futures::future::select(Box::pin(server), stop).await; + // We drop the server first to prevent a situation where main thread terminates + // before the server is properly dropped (see #504 for more details) + drop(result); + let _ = wait_signal.send(()); + }; + + use futures::FutureExt; + let fut = Box::pin(fut.map(drop)); + executor.executor().spawn(fut); let handle = InnerHandles { executor: Some(executor), @@ -256,7 +249,8 @@ impl> ServerBuilder { path: path.to_owned(), }; - match start_receiver.wait().expect("Message should always be sent") { + use futures::TryFutureExt; + match start_receiver.recv().expect("Message should always be sent") { Ok(()) => Ok(Server { handles: Arc::new(Mutex::new(handle)), wait_handle: Some(wait_receiver), @@ -270,7 +264,7 @@ impl> ServerBuilder { #[derive(Debug)] pub struct Server { handles: Arc>, - wait_handle: Option>, + wait_handle: Option>, } impl Server { @@ -288,7 +282,9 @@ impl Server { /// Wait for the server to finish pub fn wait(mut self) { - self.wait_handle.take().map(|wait_receiver| wait_receiver.wait()); + if let Some(wait_receiver) = self.wait_handle.take() { + let _ = wait_receiver.recv(); + } } } @@ -330,29 +326,16 @@ impl CloseHandle { #[cfg(test)] #[cfg(not(windows))] mod tests { - use tokio_uds; - - use self::tokio_uds::UnixStream; - use super::SecurityAttributes; - use super::{Server, ServerBuilder}; - use crate::jsonrpc::futures::sync::{mpsc, oneshot}; - use crate::jsonrpc::futures::{future, Future, Sink, Stream}; - use crate::jsonrpc::{MetaIoHandler, Value}; - use crate::meta::{MetaExtractor, NoopExtractor, RequestContext}; - use crate::server_utils::codecs; - use crate::server_utils::{ - tokio::{self, timer::Delay}, - tokio_codec::Decoder, - }; - use parking_lot::Mutex; - use std::sync::Arc; + use super::*; + + use jsonrpc_core::Value; + use std::os::unix::net::UnixStream; use std::thread; - use std::time; - use std::time::{Duration, Instant}; + use std::time::{self, Duration}; fn server_builder() -> ServerBuilder { let mut io = MetaIoHandler::<()>::default(); - io.add_method("say_hello", |_params| Ok(Value::String("hello".to_string()))); + io.add_sync_method("say_hello", |_params| Ok(Value::String("hello".to_string()))); ServerBuilder::new(io) } @@ -363,17 +346,22 @@ mod tests { } fn dummy_request_str(path: &str, data: &str) -> String { - let stream_future = UnixStream::connect(path); - let reply = stream_future.and_then(|stream| { - let stream = codecs::StreamCodec::stream_incoming().framed(stream); - let reply = stream - .send(data.to_owned()) - .and_then(move |stream| stream.into_future().map_err(|(err, _)| err)) - .and_then(|(reply, _)| future::ok(reply.expect("there should be one reply"))); - reply - }); + use futures::SinkExt; + + let reply = async move { + use tokio::net::UnixStream; + + let stream: UnixStream = UnixStream::connect(path).await?; + let codec = codecs::StreamCodec::stream_incoming(); + let mut stream = tokio_util::codec::Decoder::framed(codec, stream); + stream.send(data.to_owned()).await?; + let (reply, _) = stream.into_future().await; - reply.wait().expect("wait for reply") + reply.expect("there should be one reply") + }; + + let mut rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(reply).expect("wait for reply") } #[test] @@ -381,7 +369,7 @@ mod tests { crate::logger::init_log(); let mut io = MetaIoHandler::<()>::default(); - io.add_method("say_hello", |_params| Ok(Value::String("hello".to_string()))); + io.add_sync_method("say_hello", |_params| Ok(Value::String("hello".to_string()))); let server = ServerBuilder::new(io); let _server = server @@ -395,7 +383,7 @@ mod tests { let path = "/tmp/test-ipc-30000"; let _server = run(path); - UnixStream::connect(path).wait().expect("Socket should connect"); + UnixStream::connect(path).expect("Socket should connect"); } #[test] @@ -403,7 +391,7 @@ mod tests { crate::logger::init_log(); let path = "/tmp/test-ipc-40000"; let server = run(path); - let (stop_signal, stop_receiver) = oneshot::channel(); + let (stop_signal, stop_receiver) = std::sync::mpsc::channel(); let t = thread::spawn(move || { let result = dummy_request_str( @@ -414,15 +402,13 @@ mod tests { }); t.join().unwrap(); - let _ = stop_receiver - .map(move |result: String| { - assert_eq!( - result, "{\"jsonrpc\":\"2.0\",\"result\":\"hello\",\"id\":1}", - "Response does not exactly match the expected response", - ); - server.close(); - }) - .wait(); + let result = stop_receiver.recv().unwrap(); + + assert_eq!( + result, "{\"jsonrpc\":\"2.0\",\"result\":\"hello\",\"id\":1}", + "Response does not exactly match the expected response", + ); + server.close(); } #[test] @@ -430,7 +416,7 @@ mod tests { crate::logger::init_log(); let path = "/tmp/test-ipc-45000"; let server = run(path); - let (stop_signal, stop_receiver) = mpsc::channel(400); + let (stop_signal, stop_receiver) = futures::channel::mpsc::channel(400); let mut handles = Vec::new(); for _ in 0..4 { @@ -451,16 +437,20 @@ mod tests { handle.join().unwrap(); } - let _ = stop_receiver - .map(|result| { - assert_eq!( - result, "{\"jsonrpc\":\"2.0\",\"result\":\"hello\",\"id\":1}", - "Response does not exactly match the expected response", - ); - }) - .take(400) - .collect() - .wait(); + thread::spawn(move || { + let fut = stop_receiver + .map(|result| { + assert_eq!( + result, "{\"jsonrpc\":\"2.0\",\"result\":\"hello\",\"id\":1}", + "Response does not exactly match the expected response", + ); + }) + .take(400) + .for_each(|_| async {}); + futures::executor::block_on(fut); + }) + .join() + .unwrap(); server.close(); } @@ -476,7 +466,7 @@ mod tests { "There should be no socket file left" ); assert!( - UnixStream::connect(path).wait().is_err(), + UnixStream::connect(path).is_err(), "Connection to the closed socket should fail" ); } @@ -505,7 +495,7 @@ mod tests { let path = "/tmp/test-ipc-60000"; let mut io = MetaIoHandler::<()>::default(); - io.add_method("say_huge_hello", |_params| Ok(Value::String(huge_response_test_str()))); + io.add_sync_method("say_huge_hello", |_params| Ok(Value::String(huge_response_test_str()))); let builder = ServerBuilder::new(io); let server = builder.start(path).expect("Server must run with no issues"); @@ -521,16 +511,19 @@ mod tests { }); t.join().unwrap(); - let _ = stop_receiver - .map(move |result: String| { + thread::spawn(move || { + futures::executor::block_on(async move { + let result = stop_receiver.await.unwrap(); assert_eq!( result, huge_response_test_json(), "Response does not exactly match the expected response", ); server.close(); - }) - .wait(); + }); + }) + .join() + .unwrap(); } #[test] @@ -547,7 +540,7 @@ mod tests { } struct SessionEndExtractor { - drop_receivers: Arc>>>, + drop_receivers: Arc>>>, } impl MetaExtractor> for SessionEndExtractor { @@ -563,7 +556,7 @@ mod tests { crate::logger::init_log(); let path = "/tmp/test-ipc-30009"; - let (signal, receiver) = mpsc::channel(16); + let (signal, receiver) = futures::channel::mpsc::channel(16); let session_metadata_extractor = SessionEndExtractor { drop_receivers: Arc::new(Mutex::new(signal)), }; @@ -572,15 +565,17 @@ mod tests { let builder = ServerBuilder::with_meta_extractor(io, session_metadata_extractor); let server = builder.start(path).expect("Server must run with no issues"); { - let _ = UnixStream::connect(path).wait().expect("Socket should connect"); + let _ = UnixStream::connect(path).expect("Socket should connect"); } - receiver - .into_future() - .map_err(|_| ()) - .and_then(|drop_receiver| drop_receiver.0.unwrap().map_err(|_| ())) - .wait() - .unwrap(); + thread::spawn(move || { + futures::executor::block_on(async move { + let (drop_receiver, ..) = receiver.into_future().await; + drop_receiver.unwrap().await.unwrap(); + }); + }) + .join() + .unwrap(); server.close(); } @@ -592,7 +587,7 @@ mod tests { let handle = server.close_handle(); handle.close(); assert!( - UnixStream::connect(path).wait().is_err(), + UnixStream::connect(path).is_err(), "Connection to the closed socket should fail" ); } @@ -614,23 +609,24 @@ mod tests { tx.send(true).expect("failed to report that the server has stopped"); }); - let delay = Delay::new(Instant::now() + Duration::from_millis(500)) - .map(|_| false) - .map_err(|err| panic!("{:?}", err)); - - let result_fut = rx.map_err(|_| ()).select(delay).then(move |result| match result { - Ok((result, _)) => { - assert_eq!(result, true, "Wait timeout exceeded"); - assert!( - UnixStream::connect(path).wait().is_err(), - "Connection to the closed socket should fail" - ); - Ok(()) + let mut rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async move { + let timeout = tokio::time::delay_for(Duration::from_millis(500)); + + match futures::future::select(rx, timeout).await { + futures::future::Either::Left((result, _)) => { + assert!(result.is_ok(), "Rx failed"); + assert_eq!(result, Ok(true), "Wait timeout exceeded"); + assert!( + UnixStream::connect(path).is_err(), + "Connection to the closed socket should fail" + ); + Ok(()) + } + futures::future::Either::Right(_) => Err("timed out"), } - Err(_) => Err(()), - }); - - tokio::run(result_fut); + }) + .unwrap(); } #[test] diff --git a/pubsub/Cargo.toml b/pubsub/Cargo.toml index 589dd367d..7ecd921c6 100644 --- a/pubsub/Cargo.toml +++ b/pubsub/Cargo.toml @@ -8,19 +8,20 @@ keywords = ["jsonrpc", "json-rpc", "json", "rpc", "macros"] license = "MIT" name = "jsonrpc-pubsub" repository = "https://github.com/paritytech/jsonrpc" -version = "14.2.0" +version = "17.1.0" [dependencies] +futures = { version = "0.3", features = ["thread-pool"] } +jsonrpc-core = { version = "17.1", path = "../core" } +lazy_static = "1.4" log = "0.4" -parking_lot = "0.10.0" -jsonrpc-core = { version = "14.2", path = "../core" } -serde = "1.0" +parking_lot = "0.11.0" rand = "0.7" +serde = "1.0" [dev-dependencies] -jsonrpc-tcp-server = { version = "14.2", path = "../tcp" } -futures = { version = "0.3", features = ["compat", "thread-pool"] } -lazy_static = "1.4" +jsonrpc-tcp-server = { version = "17.1", path = "../tcp" } +serde_json = "1.0" [badges] travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} diff --git a/pubsub/examples/pubsub.rs b/pubsub/examples/pubsub.rs index 6e78a20ce..60d17cde6 100644 --- a/pubsub/examples/pubsub.rs +++ b/pubsub/examples/pubsub.rs @@ -5,8 +5,6 @@ use jsonrpc_core::*; use jsonrpc_pubsub::{PubSubHandler, Session, Subscriber, SubscriptionId}; use jsonrpc_tcp_server::{RequestContext, ServerBuilder}; -use jsonrpc_core::futures::Future; - /// To test the server: /// /// ```bash @@ -16,7 +14,7 @@ use jsonrpc_core::futures::Future; /// ``` fn main() { let mut io = PubSubHandler::new(MetaIoHandler::default()); - io.add_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); + io.add_sync_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); let is_done = Arc::new(atomic::AtomicBool::default()); let is_done2 = is_done.clone(); @@ -36,17 +34,17 @@ fn main() { let is_done = is_done.clone(); thread::spawn(move || { - let sink = subscriber.assign_id_async(SubscriptionId::Number(5)).wait().unwrap(); + let sink = subscriber.assign_id(SubscriptionId::Number(5)).unwrap(); // or subscriber.reject(Error {} ); // or drop(subscriber) loop { - if is_done.load(atomic::Ordering::AcqRel) { + if is_done.load(atomic::Ordering::SeqCst) { return; } thread::sleep(time::Duration::from_millis(100)); - match sink.notify(Params::Array(vec![Value::Number(10.into())])).wait() { + match sink.notify(Params::Array(vec![Value::Number(10.into())])) { Ok(_) => {} Err(_) => { println!("Subscription has ended, finishing."); @@ -58,7 +56,7 @@ fn main() { }), ("remove_hello", move |_id: SubscriptionId, _| { println!("Closing subscription"); - is_done2.store(true, atomic::Ordering::AcqRel); + is_done2.store(true, atomic::Ordering::SeqCst); futures::future::ok(Value::Bool(true)) }), ); diff --git a/pubsub/examples/pubsub_simple.rs b/pubsub/examples/pubsub_simple.rs index bfd8fadac..bfd5bae1d 100644 --- a/pubsub/examples/pubsub_simple.rs +++ b/pubsub/examples/pubsub_simple.rs @@ -5,8 +5,6 @@ use jsonrpc_core::*; use jsonrpc_pubsub::{PubSubHandler, Session, Subscriber, SubscriptionId}; use jsonrpc_tcp_server::{RequestContext, ServerBuilder}; -use jsonrpc_core::futures::Future; - /// To test the server: /// /// ```bash @@ -18,7 +16,7 @@ use jsonrpc_core::futures::Future; /// ``` fn main() { let mut io = PubSubHandler::new(MetaIoHandler::default()); - io.add_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); + io.add_sync_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); io.add_subscription( "hello", @@ -35,13 +33,13 @@ fn main() { } thread::spawn(move || { - let sink = subscriber.assign_id_async(SubscriptionId::Number(5)).wait().unwrap(); + let sink = subscriber.assign_id(SubscriptionId::Number(5)).unwrap(); // or subscriber.reject(Error {} ); // or drop(subscriber) loop { thread::sleep(time::Duration::from_millis(100)); - match sink.notify(Params::Array(vec![Value::Number(10.into())])).wait() { + match sink.notify(Params::Array(vec![Value::Number(10.into())])) { Ok(_) => {} Err(_) => { println!("Subscription has ended, finishing."); diff --git a/pubsub/more-examples/Cargo.toml b/pubsub/more-examples/Cargo.toml index bae1e4eb4..8a222242e 100644 --- a/pubsub/more-examples/Cargo.toml +++ b/pubsub/more-examples/Cargo.toml @@ -3,12 +3,12 @@ name = "jsonrpc-pubsub-examples" description = "Examples of Publish-Subscribe extension for jsonrpc." homepage = "https://github.com/paritytech/jsonrpc" repository = "https://github.com/paritytech/jsonrpc" -version = "14.2.0" +version = "17.1.0" authors = ["tomusdrw "] license = "MIT" [dependencies] -jsonrpc-core = { version = "14.2", path = "../../core" } -jsonrpc-pubsub = { version = "14.2", path = "../" } -jsonrpc-ws-server = { version = "14.2", path = "../../ws" } -jsonrpc-ipc-server = { version = "14.2", path = "../../ipc" } +jsonrpc-core = { version = "17.1", path = "../../core" } +jsonrpc-pubsub = { version = "17.1", path = "../" } +jsonrpc-ws-server = { version = "17.1", path = "../../ws" } +jsonrpc-ipc-server = { version = "17.1", path = "../../ipc" } diff --git a/pubsub/more-examples/examples/pubsub_ipc.rs b/pubsub/more-examples/examples/pubsub_ipc.rs index bdd805287..d8798c971 100644 --- a/pubsub/more-examples/examples/pubsub_ipc.rs +++ b/pubsub/more-examples/examples/pubsub_ipc.rs @@ -9,8 +9,6 @@ use jsonrpc_core::*; use jsonrpc_ipc_server::{RequestContext, ServerBuilder, SessionId, SessionStats}; use jsonrpc_pubsub::{PubSubHandler, Session, Subscriber, SubscriptionId}; -use jsonrpc_core::futures::Future; - /// To test the server: /// /// ```bash @@ -20,7 +18,7 @@ use jsonrpc_core::futures::Future; /// ``` fn main() { let mut io = PubSubHandler::new(MetaIoHandler::default()); - io.add_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); + io.add_sync_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); io.add_subscription( "hello", @@ -37,13 +35,13 @@ fn main() { } thread::spawn(move || { - let sink = subscriber.assign_id_async(SubscriptionId::Number(5)).wait().unwrap(); + let sink = subscriber.assign_id(SubscriptionId::Number(5)).unwrap(); // or subscriber.reject(Error {} ); // or drop(subscriber) loop { thread::sleep(time::Duration::from_millis(100)); - match sink.notify(Params::Array(vec![Value::Number(10.into())])).wait() { + match sink.notify(Params::Array(vec![Value::Number(10.into())])) { Ok(_) => {} Err(_) => { println!("Subscription has ended, finishing."); @@ -53,9 +51,9 @@ fn main() { } }); }), - ("remove_hello", |_id: SubscriptionId, _meta| -> Result { + ("remove_hello", |_id: SubscriptionId, _meta| { println!("Closing subscription"); - Ok(Value::Bool(true)) + futures::future::ready(Ok(Value::Bool(true))) }), ); diff --git a/pubsub/more-examples/examples/pubsub_ws.rs b/pubsub/more-examples/examples/pubsub_ws.rs index 5b0de4405..463bb1182 100644 --- a/pubsub/more-examples/examples/pubsub_ws.rs +++ b/pubsub/more-examples/examples/pubsub_ws.rs @@ -9,8 +9,6 @@ use jsonrpc_core::*; use jsonrpc_pubsub::{PubSubHandler, Session, Subscriber, SubscriptionId}; use jsonrpc_ws_server::{RequestContext, ServerBuilder}; -use jsonrpc_core::futures::Future; - /// Use following node.js code to test: /// /// ```js @@ -36,7 +34,7 @@ use jsonrpc_core::futures::Future; /// ``` fn main() { let mut io = PubSubHandler::new(MetaIoHandler::default()); - io.add_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); + io.add_sync_method("say_hello", |_params: Params| Ok(Value::String("hello".to_string()))); io.add_subscription( "hello", @@ -53,13 +51,13 @@ fn main() { } thread::spawn(move || { - let sink = subscriber.assign_id_async(SubscriptionId::Number(5)).wait().unwrap(); + let sink = subscriber.assign_id(SubscriptionId::Number(5)).unwrap(); // or subscriber.reject(Error {} ); // or drop(subscriber) loop { thread::sleep(time::Duration::from_millis(1000)); - match sink.notify(Params::Array(vec![Value::Number(10.into())])).wait() { + match sink.notify(Params::Array(vec![Value::Number(10.into())])) { Ok(_) => {} Err(_) => { println!("Subscription has ended, finishing."); @@ -69,10 +67,13 @@ fn main() { } }); }), - ("remove_hello", |_id: SubscriptionId, _meta| -> BoxFuture { - println!("Closing subscription"); - Box::new(futures::future::ok(Value::Bool(true))) - }), + ( + "remove_hello", + |_id: SubscriptionId, _meta| -> BoxFuture> { + println!("Closing subscription"); + Box::pin(futures::future::ready(Ok(Value::Bool(true)))) + }, + ), ); let server = diff --git a/pubsub/src/delegates.rs b/pubsub/src/delegates.rs index d2da169fe..7df77e079 100644 --- a/pubsub/src/delegates.rs +++ b/pubsub/src/delegates.rs @@ -1,8 +1,8 @@ use std::marker::PhantomData; use std::sync::Arc; -use crate::core::futures::IntoFuture; -use crate::core::{self, Error, Metadata, Params, RemoteProcedure, RpcMethod, Value}; +use crate::core::futures::Future; +use crate::core::{self, Metadata, Params, RemoteProcedure, RpcMethod, Value}; use crate::handler::{SubscribeRpcMethod, UnsubscribeRpcMethod}; use crate::subscription::{new_subscription, Subscriber}; use crate::types::{PubSubMetadata, SubscriptionId}; @@ -29,15 +29,14 @@ impl UnsubscribeRpcMethod for DelegateSubscription where M: PubSubMetadata, F: Fn(&T, SubscriptionId, Option) -> I, - I: IntoFuture, + I: Future> + Send + 'static, T: Send + Sync + 'static, F: Send + Sync + 'static, - I::Future: Send + 'static, { - type Out = I::Future; + type Out = I; fn call(&self, id: SubscriptionId, meta: Option) -> Self::Out { let closure = &self.closure; - closure(&self.delegate, id, meta).into_future() + closure(&self.delegate, id, meta) } } @@ -72,9 +71,8 @@ where Sub: Fn(&T, Params, M, Subscriber), Sub: Send + Sync + 'static, Unsub: Fn(&T, SubscriptionId, Option) -> I, - I: IntoFuture, + I: Future> + Send + 'static, Unsub: Send + Sync + 'static, - I::Future: Send + 'static, { let (sub, unsub) = new_subscription( name, @@ -98,13 +96,13 @@ where self.inner.add_alias(from, to) } + // TODO [ToDr] Consider sync? /// Adds async method to the delegate. pub fn add_method(&mut self, name: &str, method: F) where F: Fn(&T, Params) -> I, - I: IntoFuture, + I: Future> + Send + 'static, F: Send + Sync + 'static, - I::Future: Send + 'static, { self.inner.add_method(name, method) } @@ -113,9 +111,8 @@ where pub fn add_method_with_meta(&mut self, name: &str, method: F) where F: Fn(&T, Params, M) -> I, - I: IntoFuture, + I: Future> + Send + 'static, F: Send + Sync + 'static, - I::Future: Send + 'static, { self.inner.add_method_with_meta(name, method) } diff --git a/pubsub/src/handler.rs b/pubsub/src/handler.rs index 1b3567579..912f7701f 100644 --- a/pubsub/src/handler.rs +++ b/pubsub/src/handler.rs @@ -1,5 +1,5 @@ use crate::core; -use crate::core::futures::{Future, IntoFuture}; +use crate::core::futures::Future; use crate::subscription::{new_subscription, Subscriber}; use crate::types::{PubSubMetadata, SubscriptionId}; @@ -23,7 +23,7 @@ where /// Unsubscribe handler pub trait UnsubscribeRpcMethod: Send + Sync + 'static { /// Output type - type Out: Future + Send + 'static; + type Out: Future> + Send + 'static; /// Called when client is requesting to cancel existing subscription. /// /// Metadata is not available if the session was closed without unsubscribing. @@ -33,12 +33,11 @@ pub trait UnsubscribeRpcMethod: Send + Sync + 'static { impl UnsubscribeRpcMethod for F where F: Fn(SubscriptionId, Option) -> I + Send + Sync + 'static, - I: IntoFuture, - I::Future: Send + 'static, + I: Future> + Send + 'static, { - type Out = I::Future; + type Out = I; fn call(&self, id: SubscriptionId, meta: Option) -> Self::Out { - (*self)(id, meta).into_future() + (*self)(id, meta) } } @@ -99,8 +98,8 @@ mod tests { use std::sync::Arc; use crate::core; + use crate::core::futures::channel::mpsc; use crate::core::futures::future; - use crate::core::futures::sync::mpsc; use crate::subscription::{Session, Subscriber}; use crate::types::{PubSubMetadata, SubscriptionId}; @@ -136,7 +135,7 @@ mod tests { ); // when - let (tx, _rx) = mpsc::channel(1); + let (tx, _rx) = mpsc::unbounded(); let meta = Metadata(Arc::new(Session::new(tx))); let req = r#"{"jsonrpc":"2.0","id":1,"method":"subscribe_hello","params":null}"#; let res = handler.handle_request_sync(req, meta); diff --git a/pubsub/src/manager.rs b/pubsub/src/manager.rs index 3c8d77ba2..1948dde10 100644 --- a/pubsub/src/manager.rs +++ b/pubsub/src/manager.rs @@ -21,8 +21,8 @@ use std::sync::{ Arc, }; -use crate::core::futures::sync::oneshot; -use crate::core::futures::{future as future01, Future as Future01}; +use crate::core::futures::channel::oneshot; +use crate::core::futures::{self, task, Future, FutureExt, TryFutureExt}; use crate::{ typed::{Sink, Subscriber}, SubscriptionId, @@ -33,8 +33,8 @@ use parking_lot::Mutex; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; -/// Alias for an implementation of `futures::future::Executor`. -pub type TaskExecutor = Arc + Send>> + Send + Sync>; +/// Cloneable `Spawn` handle. +pub type TaskExecutor = Arc; type ActiveSubscriptions = Arc>>>; @@ -170,23 +170,28 @@ impl SubscriptionManager { /// /// Second parameter is a function that converts Subscriber Sink into a Future. /// This future will be driven to completion by the underlying event loop - pub fn add(&self, subscriber: Subscriber, into_future: G) -> SubscriptionId + pub fn add(&self, subscriber: Subscriber, into_future: G) -> SubscriptionId where - G: FnOnce(Sink) -> R, - R: future01::IntoFuture, - F: future01::Future + Send + 'static, + G: FnOnce(Sink) -> F, + F: Future + Send + 'static, { let id = self.id_provider.next_id(); let subscription_id: SubscriptionId = id.into(); if let Ok(sink) = subscriber.assign_id(subscription_id.clone()) { let (tx, rx) = oneshot::channel(); - let future = into_future(sink) - .into_future() - .select(rx.map_err(|e| warn!("Error timing out: {:?}", e))) - .then(|_| Ok(())); + let f = into_future(sink).fuse(); + let rx = rx.map_err(|e| warn!("Error timing out: {:?}", e)).fuse(); + let future = async move { + futures::pin_mut!(f); + futures::pin_mut!(rx); + futures::select! { + a = f => a, + _ = rx => (), + } + }; self.active_subscriptions.lock().insert(subscription_id.clone(), tx); - if self.executor.execute(Box::new(future)).is_err() { + if self.executor.spawn_obj(task::FutureObj::new(Box::pin(future))).is_err() { error!("Failed to spawn RPC subscription task"); } } @@ -222,11 +227,8 @@ impl SubscriptionManager { mod tests { use super::*; use crate::typed::Subscriber; - use futures::{compat::Future01CompatExt, executor, FutureExt}; - use futures::{stream, StreamExt, TryStreamExt}; - - use crate::core::futures::sink::Sink as Sink01; - use crate::core::futures::stream::Stream as Stream01; + use futures::{executor, stream}; + use futures::{FutureExt, StreamExt}; // Executor shared by all tests. // @@ -238,12 +240,13 @@ mod tests { } pub struct TestTaskExecutor; - type Boxed01Future01 = Box + Send + 'static>; + impl task::Spawn for TestTaskExecutor { + fn spawn_obj(&self, future: task::FutureObj<'static, ()>) -> Result<(), task::SpawnError> { + EXECUTOR.spawn_obj(future) + } - impl future01::Executor for TestTaskExecutor { - fn execute(&self, future: Boxed01Future01) -> std::result::Result<(), future01::ExecuteError> { - EXECUTOR.spawn_ok(future.compat().map(drop)); - Ok(()) + fn status(&self) -> Result<(), task::SpawnError> { + EXECUTOR.status() } } @@ -296,13 +299,9 @@ mod tests { fn new_subscription_manager_defaults_to_random_string_provider() { let manager = SubscriptionManager::new(Arc::new(TestTaskExecutor)); let subscriber = Subscriber::::new_test("test_subTest").0; - let stream = stream::iter(vec![Ok(1)]).compat(); + let stream = stream::iter(vec![Ok(Ok(1))]); - let id = manager.add(subscriber, |sink| { - let stream = stream.map(|res| Ok(res)); - - sink.sink_map_err(|_| ()).send_all(stream).map(|_| ()) - }); + let id = manager.add(subscriber, move |sink| stream.forward(sink).map(|_| ())); assert!(matches!(id, SubscriptionId::String(_))) } @@ -313,13 +312,9 @@ mod tests { let manager = SubscriptionManager::with_id_provider(id_provider, Arc::new(TestTaskExecutor)); let subscriber = Subscriber::::new_test("test_subTest").0; - let stream = stream::iter(vec![Ok(1)]).compat(); + let stream = stream::iter(vec![Ok(Ok(1))]); - let id = manager.add(subscriber, |sink| { - let stream = stream.map(|res| Ok(res)); - - sink.sink_map_err(|_| ()).send_all(stream).map(|_| ()) - }); + let id = manager.add(subscriber, move |sink| stream.forward(sink).map(|_| ())); assert!(matches!(id, SubscriptionId::Number(_))) } @@ -330,13 +325,9 @@ mod tests { let manager = SubscriptionManager::with_id_provider(id_provider, Arc::new(TestTaskExecutor)); let subscriber = Subscriber::::new_test("test_subTest").0; - let stream = stream::iter(vec![Ok(1)]).compat(); - - let id = manager.add(subscriber, |sink| { - let stream = stream.map(|res| Ok(res)); + let stream = stream::iter(vec![Ok(Ok(1))]); - sink.sink_map_err(|_| ()).send_all(stream).map(|_| ()) - }); + let id = manager.add(subscriber, move |sink| stream.forward(sink).map(|_| ())); assert!(matches!(id, SubscriptionId::String(_))) } @@ -350,12 +341,9 @@ mod tests { let (mut tx, rx) = futures::channel::mpsc::channel(8); tx.start_send(1).unwrap(); - let stream = rx.map(|v| Ok::<_, ()>(v)).compat(); - - let id = manager.add(subscriber, |sink| { - let stream = stream.map(|res| Ok(res)); - - sink.sink_map_err(|_| ()).send_all(stream).map(|_| ()) + let id = manager.add(subscriber, move |sink| { + let rx = rx.map(|v| Ok(Ok(v))); + rx.forward(sink).map(|_| ()) }); let is_cancelled = manager.cancel(id); @@ -372,4 +360,11 @@ mod tests { assert!(is_not_cancelled); } + + #[test] + fn is_send_sync() { + fn send_sync() {} + + send_sync::(); + } } diff --git a/pubsub/src/oneshot.rs b/pubsub/src/oneshot.rs index 2d72f11ef..2ab4208d3 100644 --- a/pubsub/src/oneshot.rs +++ b/pubsub/src/oneshot.rs @@ -1,12 +1,12 @@ //! A futures oneshot channel that can be used for rendezvous. -use crate::core::futures::{self, future, sync::oneshot, Future}; +use crate::core::futures::{self, channel::oneshot, future, Future, FutureExt, TryFutureExt}; use std::ops::{Deref, DerefMut}; /// Create a new future-base rendezvous channel. /// /// The returned `Sender` and `Receiver` objects are wrapping -/// the regular `futures::sync::oneshot` counterparts and have the same functionality. +/// the regular `futures::channel::oneshot` counterparts and have the same functionality. /// Additionaly `Sender::send_and_wait` allows you to send a message to the channel /// and get a future that resolves when the message is consumed. pub fn channel() -> (Sender, Receiver) { @@ -49,14 +49,14 @@ impl Sender { /// to send the message as that happens synchronously. /// The future resolves to error in case the receiving end was dropped before /// being able to process the message. - pub fn send_and_wait(self, t: T) -> impl Future { + pub fn send_and_wait(self, t: T) -> impl Future> { let Self { sender, receipt } = self; if let Err(_) = sender.send(t) { - return future::Either::A(future::err(())); + return future::Either::Left(future::ready(Err(()))); } - future::Either::B(receipt.map_err(|_| ())) + future::Either::Right(receipt.map_err(|_| ())) } } @@ -88,18 +88,13 @@ pub struct Receiver { } impl Future for Receiver { - type Item = as Future>::Item; - type Error = as Future>::Error; + type Output = as Future>::Output; - fn poll(&mut self) -> futures::Poll { - match self.receiver.poll() { - Ok(futures::Async::Ready(r)) => { - if let Some(receipt) = self.receipt.take() { - let _ = receipt.send(()); - } - Ok(futures::Async::Ready(r)) - } - e => e, + fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut futures::task::Context) -> futures::task::Poll { + let r = futures::ready!(self.receiver.poll_unpin(cx))?; + if let Some(receipt) = self.receipt.take() { + let _ = receipt.send(()); } + Ok(r).into() } } diff --git a/pubsub/src/subscription.rs b/pubsub/src/subscription.rs index 473f0c47d..14e6ddcbb 100644 --- a/pubsub/src/subscription.rs +++ b/pubsub/src/subscription.rs @@ -3,15 +3,25 @@ use parking_lot::Mutex; use std::collections::HashMap; use std::fmt; +use std::pin::Pin; use std::sync::Arc; -use crate::core::futures::sync::mpsc; -use crate::core::futures::{self, future, Future, Sink as FuturesSink}; +use crate::core::futures::channel::mpsc; +use crate::core::futures::{ + self, future, + task::{Context, Poll}, + Future, Sink as FuturesSink, TryFutureExt, +}; use crate::core::{self, BoxFuture}; use crate::handler::{SubscribeRpcMethod, UnsubscribeRpcMethod}; use crate::types::{PubSubMetadata, SinkResult, SubscriptionId, TransportError, TransportSender}; +lazy_static::lazy_static! { + static ref UNSUBSCRIBE_POOL: futures::executor::ThreadPool = futures::executor::ThreadPool::new() + .expect("Unable to spawn background pool for unsubscribe tasks."); +} + /// RPC client session /// Keeps track of active subscriptions and unsubscribes from them upon dropping. pub struct Session { @@ -71,8 +81,11 @@ impl Session { } /// Removes existing subscription. - fn remove_subscription(&self, name: &str, id: &SubscriptionId) { - self.active_subscriptions.lock().remove(&(id.clone(), name.into())); + fn remove_subscription(&self, name: &str, id: &SubscriptionId) -> bool { + self.active_subscriptions + .lock() + .remove(&(id.clone(), name.into())) + .is_some() } } @@ -101,40 +114,37 @@ impl Sink { /// Sends a notification to a client. pub fn notify(&self, val: core::Params) -> SinkResult { let val = self.params_to_string(val); - self.transport.clone().send(val.0) + self.transport.clone().unbounded_send(val) } - fn params_to_string(&self, val: core::Params) -> (String, core::Params) { + fn params_to_string(&self, val: core::Params) -> String { let notification = core::Notification { jsonrpc: Some(core::Version::V2), method: self.notification.clone(), params: val, }; - ( - core::to_string(¬ification).expect("Notification serialization never fails."), - notification.params, - ) + core::to_string(¬ification).expect("Notification serialization never fails.") } } -impl FuturesSink for Sink { - type SinkItem = core::Params; - type SinkError = TransportError; +impl FuturesSink for Sink { + type Error = TransportError; - fn start_send(&mut self, item: Self::SinkItem) -> futures::StartSend { - let (val, params) = self.params_to_string(item); - self.transport.start_send(val).map(|result| match result { - futures::AsyncSink::Ready => futures::AsyncSink::Ready, - futures::AsyncSink::NotReady(_) => futures::AsyncSink::NotReady(params), - }) + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.transport).poll_ready(cx) } - fn poll_complete(&mut self) -> futures::Poll<(), Self::SinkError> { - self.transport.poll_complete() + fn start_send(mut self: Pin<&mut Self>, item: core::Params) -> Result<(), Self::Error> { + let val = self.params_to_string(item); + Pin::new(&mut self.transport).start_send(val) } - fn close(&mut self) -> futures::Poll<(), Self::SinkError> { - self.transport.close() + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.transport).poll_flush(cx) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.transport).poll_close(cx) } } @@ -156,10 +166,10 @@ impl Subscriber { ) -> ( Self, crate::oneshot::Receiver>, - mpsc::Receiver, + mpsc::UnboundedReceiver, ) { let (sender, id_receiver) = crate::oneshot::channel(); - let (transport, transport_receiver) = mpsc::channel(1); + let (transport, transport_receiver) = mpsc::unbounded(); let subscriber = Subscriber { notification: method.into(), @@ -192,19 +202,16 @@ impl Subscriber { /// /// The returned `Future` resolves when the subscriber receives subscription id. /// Resolves to `Err` if request has already terminated. - pub fn assign_id_async(self, id: SubscriptionId) -> impl Future { + pub fn assign_id_async(self, id: SubscriptionId) -> impl Future> { let Self { notification, transport, sender, } = self; - sender - .send_and_wait(Ok(id)) - .map(|_| Sink { - notification, - transport, - }) - .map_err(|_| ()) + sender.send_and_wait(Ok(id)).map_ok(|_| Sink { + notification, + transport, + }) } /// Rejects this subscription request with given error. @@ -218,8 +225,8 @@ impl Subscriber { /// /// The returned `Future` resolves when the rejection is sent to the client. /// Resolves to `Err` if request has already terminated. - pub fn reject_async(self, error: core::Error) -> impl Future { - self.sender.send_and_wait(Err(error)).map(|_| ()).map_err(|_| ()) + pub fn reject_async(self, error: core::Error) -> impl Future> { + self.sender.send_and_wait(Err(error)).map_ok(|_| ()).map_err(|_| ()) } } @@ -274,7 +281,7 @@ where F: SubscribeRpcMethod, G: UnsubscribeRpcMethod, { - fn call(&self, params: core::Params, meta: M) -> BoxFuture { + fn call(&self, params: core::Params, meta: M) -> BoxFuture> { match meta.session() { Some(session) => { let (tx, rx) = crate::oneshot::channel(); @@ -290,19 +297,25 @@ where let unsub = self.unsubscribe.clone(); let notification = self.notification.clone(); let subscribe_future = rx.map_err(|_| subscription_rejected()).and_then(move |result| { - futures::done(match result { + futures::future::ready(match result { Ok(id) => { session.add_subscription(¬ification, &id, move |id| { - let _ = unsub.call(id, None).wait(); + // TODO [#570] [ToDr] We currently run unsubscribe tasks on a shared thread pool. + // In the future we should use some kind of `::spawn` method + // that spawns a task on an existing executor or pass the spawner handle here. + let f = unsub.call(id, None); + UNSUBSCRIBE_POOL.spawn_ok(async move { + let _ = f.await; + }); }); Ok(id.into()) } Err(e) => Err(e), }) }); - Box::new(subscribe_future) + Box::pin(subscribe_future) } - None => Box::new(future::err(subscriptions_unavailable())), + None => Box::pin(future::err(subscriptions_unavailable())), } } } @@ -318,18 +331,21 @@ where M: PubSubMetadata, G: UnsubscribeRpcMethod, { - fn call(&self, params: core::Params, meta: M) -> BoxFuture { + fn call(&self, params: core::Params, meta: M) -> BoxFuture> { let id = match params { core::Params::Array(ref vec) if vec.len() == 1 => SubscriptionId::parse_value(&vec[0]), _ => None, }; match (meta.session(), id) { (Some(session), Some(id)) => { - session.remove_subscription(&self.notification, &id); - Box::new(self.unsubscribe.call(id, Some(meta))) + if session.remove_subscription(&self.notification, &id) { + Box::pin(self.unsubscribe.call(id, Some(meta))) + } else { + Box::pin(future::err(core::Error::invalid_params("Invalid subscription id."))) + } } - (Some(_), None) => Box::new(future::err(core::Error::invalid_params("Expected subscription id."))), - _ => Box::new(future::err(subscriptions_unavailable())), + (Some(_), None) => Box::pin(future::err(core::Error::invalid_params("Expected subscription id."))), + _ => Box::pin(future::err(subscriptions_unavailable())), } } } @@ -337,8 +353,7 @@ where #[cfg(test)] mod tests { use crate::core; - use crate::core::futures::sync::mpsc; - use crate::core::futures::{Async, Future, Stream}; + use crate::core::futures::channel::mpsc; use crate::core::RpcMethod; use crate::types::{PubSubMetadata, SubscriptionId}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -346,8 +361,8 @@ mod tests { use super::{new_subscription, Session, Sink, Subscriber}; - fn session() -> (Session, mpsc::Receiver) { - let (tx, rx) = mpsc::channel(1); + fn session() -> (Session, mpsc::UnboundedReceiver) { + let (tx, rx) = mpsc::unbounded(); (Session::new(tx), rx) } @@ -383,13 +398,36 @@ mod tests { }); // when - session.remove_subscription("test", &id); + let removed = session.remove_subscription("test", &id); drop(session); // then + assert_eq!(removed, true); assert_eq!(called.load(Ordering::SeqCst), false); } + #[test] + fn should_not_remove_subscription_if_invalid() { + // given + let id = SubscriptionId::Number(1); + let called = Arc::new(AtomicBool::new(false)); + let called2 = called.clone(); + let other_session = session().0; + let session = session().0; + session.add_subscription("test", &id, move |id| { + assert_eq!(id, SubscriptionId::Number(1)); + called2.store(true, Ordering::SeqCst); + }); + + // when + let removed = other_session.remove_subscription("test", &id); + drop(session); + + // then + assert_eq!(removed, false); + assert_eq!(called.load(Ordering::SeqCst), true); + } + #[test] fn should_unregister_in_case_of_collision() { // given @@ -412,7 +450,7 @@ mod tests { #[test] fn should_send_notification_to_the_transport() { // given - let (tx, mut rx) = mpsc::channel(1); + let (tx, mut rx) = mpsc::unbounded(); let sink = Sink { notification: "test".into(), transport: tx, @@ -420,21 +458,18 @@ mod tests { // when sink.notify(core::Params::Array(vec![core::Value::Number(10.into())])) - .wait() .unwrap(); + let val = rx.try_next().unwrap(); // then - assert_eq!( - rx.poll().unwrap(), - Async::Ready(Some(r#"{"jsonrpc":"2.0","method":"test","params":[10]}"#.into())) - ); + assert_eq!(val, Some(r#"{"jsonrpc":"2.0","method":"test","params":[10]}"#.into())); } #[test] fn should_assign_id() { // given - let (transport, _) = mpsc::channel(1); - let (tx, mut rx) = crate::oneshot::channel(); + let (transport, _) = mpsc::unbounded(); + let (tx, rx) = crate::oneshot::channel(); let subscriber = Subscriber { notification: "test".into(), transport, @@ -445,16 +480,19 @@ mod tests { let sink = subscriber.assign_id_async(SubscriptionId::Number(5)); // then - assert_eq!(rx.poll().unwrap(), Async::Ready(Ok(SubscriptionId::Number(5)))); - let sink = sink.wait().unwrap(); - assert_eq!(sink.notification, "test".to_owned()); + futures::executor::block_on(async move { + let id = rx.await; + assert_eq!(id, Ok(Ok(SubscriptionId::Number(5)))); + let sink = sink.await.unwrap(); + assert_eq!(sink.notification, "test".to_owned()); + }) } #[test] fn should_reject() { // given - let (transport, _) = mpsc::channel(1); - let (tx, mut rx) = crate::oneshot::channel(); + let (transport, _) = mpsc::unbounded(); + let (tx, rx) = crate::oneshot::channel(); let subscriber = Subscriber { notification: "test".into(), transport, @@ -470,44 +508,92 @@ mod tests { let reject = subscriber.reject_async(error.clone()); // then - assert_eq!(rx.poll().unwrap(), Async::Ready(Err(error))); - reject.wait().unwrap(); + futures::executor::block_on(async move { + assert_eq!(rx.await.unwrap(), Err(error)); + reject.await.unwrap(); + }); } - #[derive(Clone, Default)] - struct Metadata; + #[derive(Clone)] + struct Metadata(Arc); impl core::Metadata for Metadata {} impl PubSubMetadata for Metadata { fn session(&self) -> Option> { - Some(Arc::new(session().0)) + Some(self.0.clone()) + } + } + impl Default for Metadata { + fn default() -> Self { + Self(Arc::new(session().0)) } } #[test] fn should_subscribe() { // given - let called = Arc::new(AtomicBool::new(false)); - let called2 = called.clone(); let (subscribe, _) = new_subscription( "test".into(), - move |params, _meta, _subscriber| { + move |params, _meta, subscriber: Subscriber| { assert_eq!(params, core::Params::None); - called2.store(true, Ordering::SeqCst); + let _sink = subscriber.assign_id(SubscriptionId::Number(5)).unwrap(); }, - |_id, _meta| Ok(core::Value::Bool(true)), + |_id, _meta| async { Ok(core::Value::Bool(true)) }, ); - let meta = Metadata; // when + let meta = Metadata::default(); let result = subscribe.call(core::Params::None, meta); // then - assert_eq!(called.load(Ordering::SeqCst), true); + assert_eq!(futures::executor::block_on(result), Ok(serde_json::json!(5))); + } + + #[test] + fn should_unsubscribe() { + // given + const SUB_ID: u64 = 5; + let (subscribe, unsubscribe) = new_subscription( + "test".into(), + move |params, _meta, subscriber: Subscriber| { + assert_eq!(params, core::Params::None); + let _sink = subscriber.assign_id(SubscriptionId::Number(SUB_ID)).unwrap(); + }, + |_id, _meta| async { Ok(core::Value::Bool(true)) }, + ); + + // when + let meta = Metadata::default(); + futures::executor::block_on(subscribe.call(core::Params::None, meta.clone())).unwrap(); + let result = unsubscribe.call(core::Params::Array(vec![serde_json::json!(SUB_ID)]), meta); + + // then + assert_eq!(futures::executor::block_on(result), Ok(serde_json::json!(true))); + } + + #[test] + fn should_not_unsubscribe_if_invalid() { + // given + const SUB_ID: u64 = 5; + let (subscribe, unsubscribe) = new_subscription( + "test".into(), + move |params, _meta, subscriber: Subscriber| { + assert_eq!(params, core::Params::None); + let _sink = subscriber.assign_id(SubscriptionId::Number(SUB_ID)).unwrap(); + }, + |_id, _meta| async { Ok(core::Value::Bool(true)) }, + ); + + // when + let meta = Metadata::default(); + futures::executor::block_on(subscribe.call(core::Params::None, meta.clone())).unwrap(); + let result = unsubscribe.call(core::Params::Array(vec![serde_json::json!(SUB_ID + 1)]), meta); + + // then assert_eq!( - result.wait(), + futures::executor::block_on(result), Err(core::Error { - code: core::ErrorCode::ServerError(-32091), - message: "Subscription rejected".into(), + code: core::ErrorCode::InvalidParams, + message: "Invalid subscription id.".into(), data: None, }) ); diff --git a/pubsub/src/typed.rs b/pubsub/src/typed.rs index a2c5804a2..e64b6f81e 100644 --- a/pubsub/src/typed.rs +++ b/pubsub/src/typed.rs @@ -1,12 +1,14 @@ //! PUB-SUB auto-serializing structures. use std::marker::PhantomData; +use std::pin::Pin; use crate::subscription; use crate::types::{SinkResult, SubscriptionId, TransportError}; use serde; -use crate::core::futures::{self, sync, Future, Sink as FuturesSink}; +use crate::core::futures::task::{Context, Poll}; +use crate::core::futures::{self, channel}; use crate::core::{self, Error, Params, Value}; /// New PUB-SUB subscriber. @@ -31,7 +33,7 @@ impl Subscriber { ) -> ( Self, crate::oneshot::Receiver>, - sync::mpsc::Receiver, + channel::mpsc::UnboundedReceiver, ) { let (subscriber, id, subscription) = subscription::Subscriber::new_test(method); (Subscriber::new(subscriber), id, subscription) @@ -45,18 +47,18 @@ impl Subscriber { /// Reject subscription with given error. /// /// The returned future will resolve when the response is sent to the client. - pub fn reject_async(self, error: Error) -> impl Future { - self.subscriber.reject_async(error) + pub async fn reject_async(self, error: Error) -> Result<(), ()> { + self.subscriber.reject_async(error).await } /// Assign id to this subscriber. /// This method consumes `Subscriber` and returns `Sink` /// if the connection is still open or error otherwise. pub fn assign_id(self, id: SubscriptionId) -> Result, ()> { - self.subscriber.assign_id(id.clone()).map(|sink| Sink { + let sink = self.subscriber.assign_id(id.clone())?; + Ok(Sink { id, sink, - buffered: None, _data: PhantomData, }) } @@ -64,11 +66,11 @@ impl Subscriber { /// Assign id to this subscriber. /// This method consumes `Subscriber` and resolves to `Sink` /// if the connection is still open and the id has been sent or to error otherwise. - pub fn assign_id_async(self, id: SubscriptionId) -> impl Future, Error = ()> { - self.subscriber.assign_id_async(id.clone()).map(|sink| Sink { + pub async fn assign_id_async(self, id: SubscriptionId) -> Result, ()> { + let sink = self.subscriber.assign_id_async(id.clone()).await?; + Ok(Sink { id, sink, - buffered: None, _data: PhantomData, }) } @@ -79,7 +81,6 @@ impl Subscriber { pub struct Sink { sink: subscription::Sink, id: SubscriptionId, - buffered: Option, _data: PhantomData<(T, E)>, } @@ -112,49 +113,25 @@ impl Sink { .collect(), ) } - - fn poll(&mut self) -> futures::Poll<(), TransportError> { - if let Some(item) = self.buffered.take() { - let result = self.sink.start_send(item)?; - if let futures::AsyncSink::NotReady(item) = result { - self.buffered = Some(item); - } - } - - if self.buffered.is_some() { - Ok(futures::Async::NotReady) - } else { - Ok(futures::Async::Ready(())) - } - } } -impl futures::sink::Sink for Sink { - type SinkItem = Result; - type SinkError = TransportError; +impl futures::sink::Sink> for Sink { + type Error = TransportError; - fn start_send(&mut self, item: Self::SinkItem) -> futures::StartSend { - // Make sure to always try to process the buffered entry. - // Since we're just a proxy to real `Sink` we don't need - // to schedule a `Task` wakeup. It will be done downstream. - if self.poll()?.is_not_ready() { - return Ok(futures::AsyncSink::NotReady(item)); - } + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.sink).poll_ready(cx) + } + fn start_send(mut self: Pin<&mut Self>, item: Result) -> Result<(), Self::Error> { let val = self.val_to_params(item); - self.buffered = Some(val); - self.poll()?; - - Ok(futures::AsyncSink::Ready) + Pin::new(&mut self.sink).start_send(val) } - fn poll_complete(&mut self) -> futures::Poll<(), Self::SinkError> { - self.poll()?; - self.sink.poll_complete() + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.sink).poll_flush(cx) } - fn close(&mut self) -> futures::Poll<(), Self::SinkError> { - self.poll()?; - self.sink.close() + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.sink).poll_close(cx) } } diff --git a/pubsub/src/types.rs b/pubsub/src/types.rs index 9e1437e74..f6606801c 100644 --- a/pubsub/src/types.rs +++ b/pubsub/src/types.rs @@ -1,15 +1,15 @@ use crate::core; -use crate::core::futures::sync::mpsc; +use crate::core::futures::channel::mpsc; use std::sync::Arc; use crate::subscription::Session; /// Raw transport sink for specific client. -pub type TransportSender = mpsc::Sender; +pub type TransportSender = mpsc::UnboundedSender; /// Raw transport error. -pub type TransportError = mpsc::SendError; +pub type TransportError = mpsc::SendError; /// Subscription send result. -pub type SinkResult = core::futures::sink::Send; +pub type SinkResult = Result<(), mpsc::TrySendError>; /// Metadata extension for pub-sub method handling. /// diff --git a/server-utils/Cargo.toml b/server-utils/Cargo.toml index 72dcf1f11..aacbce4f0 100644 --- a/server-utils/Cargo.toml +++ b/server-utils/Cargo.toml @@ -8,16 +8,18 @@ keywords = ["jsonrpc", "json-rpc", "json", "rpc", "serde"] license = "MIT" name = "jsonrpc-server-utils" repository = "https://github.com/paritytech/jsonrpc" -version = "14.2.0" +version = "17.1.0" [dependencies] -bytes = "0.4" +bytes = "0.5" +futures = "0.3" globset = "0.4" -jsonrpc-core = { version = "14.2", path = "../core" } +jsonrpc-core = { version = "17.1", path = "../core" } lazy_static = "1.1.0" log = "0.4" -tokio = { version = "0.1.15" } -tokio-codec = { version = "0.1" } +tokio = { version = "0.2", features = ["rt-threaded", "io-driver", "io-util", "time", "tcp"] } +tokio-util = { version = "0.3", features = ["codec"] } + unicase = "2.0" [badges] diff --git a/server-utils/src/lib.rs b/server-utils/src/lib.rs index 4a1e2a0e1..5c9f52d1a 100644 --- a/server-utils/src/lib.rs +++ b/server-utils/src/lib.rs @@ -8,10 +8,8 @@ extern crate log; #[macro_use] extern crate lazy_static; -use jsonrpc_core as core; - pub use tokio; -pub use tokio_codec; +pub use tokio_util; pub mod cors; pub mod hosts; diff --git a/server-utils/src/reactor.rs b/server-utils/src/reactor.rs index f66c73c12..df8afd408 100644 --- a/server-utils/src/reactor.rs +++ b/server-utils/src/reactor.rs @@ -6,15 +6,16 @@ //! that `tokio::runtime` can be multi-threaded. use std::io; -use tokio; -use crate::core::futures::{self, Future}; +use tokio::runtime; +/// Task executor for Tokio 0.2 runtime. +pub type TaskExecutor = tokio::runtime::Handle; /// Possibly uninitialized event loop executor. #[derive(Debug)] pub enum UninitializedExecutor { /// Shared instance of executor. - Shared(tokio::runtime::TaskExecutor), + Shared(TaskExecutor), /// Event Loop should be spawned by the transport. Unspawned, } @@ -42,28 +43,20 @@ impl UninitializedExecutor { #[derive(Debug)] pub enum Executor { /// Shared instance - Shared(tokio::runtime::TaskExecutor), + Shared(TaskExecutor), /// Spawned Event Loop Spawned(RpcEventLoop), } impl Executor { /// Get tokio executor associated with this event loop. - pub fn executor(&self) -> tokio::runtime::TaskExecutor { - match *self { + pub fn executor(&self) -> TaskExecutor { + match self { Executor::Shared(ref executor) => executor.clone(), Executor::Spawned(ref eloop) => eloop.executor(), } } - /// Spawn a future onto the Tokio runtime. - pub fn spawn(&self, future: F) - where - F: Future + Send + 'static, - { - self.executor().spawn(future) - } - /// Closes underlying event loop (if any!). pub fn close(self) { if let Executor::Spawned(eloop) = self { @@ -82,9 +75,9 @@ impl Executor { /// A handle to running event loop. Dropping the handle will cause event loop to finish. #[derive(Debug)] pub struct RpcEventLoop { - executor: tokio::runtime::TaskExecutor, - close: Option>, - handle: Option, + executor: TaskExecutor, + close: Option>, + runtime: Option, } impl Drop for RpcEventLoop { @@ -101,36 +94,46 @@ impl RpcEventLoop { /// Spawns a new named thread with the `EventLoop`. pub fn with_name(name: Option) -> io::Result { - let (stop, stopped) = futures::oneshot(); + let (stop, stopped) = futures::channel::oneshot::channel(); - let mut tb = tokio::runtime::Builder::new(); + let mut tb = runtime::Builder::new(); tb.core_threads(1); + tb.threaded_scheduler(); + tb.enable_all(); if let Some(name) = name { - tb.name_prefix(name); + tb.thread_name(name); } - let mut runtime = tb.build()?; - let executor = runtime.executor(); - let terminate = futures::empty().select(stopped).map(|_| ()).map_err(|_| ()); - runtime.spawn(terminate); - let handle = runtime.shutdown_on_idle(); + let runtime = tb.build()?; + let executor = runtime.handle().to_owned(); + + runtime.spawn(async { + let _ = stopped.await; + }); Ok(RpcEventLoop { executor, close: Some(stop), - handle: Some(handle), + runtime: Some(runtime), }) } /// Get executor for this event loop. - pub fn executor(&self) -> tokio::runtime::TaskExecutor { - self.executor.clone() + pub fn executor(&self) -> runtime::Handle { + self.runtime + .as_ref() + .expect("Runtime is only None if we're being dropped; qed") + .handle() + .clone() } /// Blocks current thread and waits until the event loop is finished. pub fn wait(mut self) -> Result<(), ()> { - self.handle.take().ok_or(())?.wait() + // Dropping Tokio 0.2 runtime waits for all spawned tasks to terminate + let runtime = self.runtime.take().ok_or(())?; + drop(runtime); + Ok(()) } /// Finishes this event loop. diff --git a/server-utils/src/stream_codec.rs b/server-utils/src/stream_codec.rs index d7cb268b9..4edef5add 100644 --- a/server-utils/src/stream_codec.rs +++ b/server-utils/src/stream_codec.rs @@ -1,6 +1,5 @@ use bytes::BytesMut; use std::{io, str}; -use tokio_codec::{Decoder, Encoder}; /// Separator for enveloping messages in streaming codecs #[derive(Debug, Clone)] @@ -48,7 +47,7 @@ fn is_whitespace(byte: u8) -> bool { } } -impl Decoder for StreamCodec { +impl tokio_util::codec::Decoder for StreamCodec { type Item = String; type Error = io::Error; @@ -56,7 +55,7 @@ impl Decoder for StreamCodec { if let Separator::Byte(separator) = self.incoming_separator { if let Some(i) = buf.as_ref().iter().position(|&b| b == separator) { let line = buf.split_to(i); - buf.split_to(1); + let _ = buf.split_to(1); match str::from_utf8(&line.as_ref()) { Ok(s) => Ok(Some(s.to_string())), @@ -108,8 +107,7 @@ impl Decoder for StreamCodec { } } -impl Encoder for StreamCodec { - type Item = String; +impl tokio_util::codec::Encoder for StreamCodec { type Error = io::Error; fn encode(&mut self, msg: String, buf: &mut BytesMut) -> io::Result<()> { @@ -127,7 +125,7 @@ mod tests { use super::StreamCodec; use bytes::{BufMut, BytesMut}; - use tokio_codec::Decoder; + use tokio_util::codec::Decoder; #[test] fn simple_encode() { diff --git a/server-utils/src/suspendable_stream.rs b/server-utils/src/suspendable_stream.rs index f563cdebe..8d3179da9 100644 --- a/server-utils/src/suspendable_stream.rs +++ b/server-utils/src/suspendable_stream.rs @@ -1,7 +1,10 @@ +use std::future::Future; use std::io; -use std::time::{Duration, Instant}; -use tokio::prelude::*; -use tokio::timer::Delay; +use std::pin::Pin; +use std::task::Poll; +use std::time::Duration; + +use tokio::time::Delay; /// `Incoming` is a stream of incoming sockets /// Polling the stream may return a temporary io::Error (for instance if we can't open the connection because of "too many open files" limit) @@ -33,38 +36,37 @@ impl SuspendableStream { } } -impl Stream for SuspendableStream +impl futures::Stream for SuspendableStream where - S: Stream, + S: futures::Stream> + Unpin, { type Item = I; - type Error = (); - fn poll(&mut self) -> Result>, ()> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll> { loop { - if let Some(mut timeout) = self.timeout.take() { - match timeout.poll() { - Ok(Async::Ready(_)) => {} - Ok(Async::NotReady) => { - self.timeout = Some(timeout); - return Ok(Async::NotReady); - } - Err(err) => { - warn!("Timeout error {:?}", err); - task::current().notify(); - return Ok(Async::NotReady); - } + if let Some(timeout) = self.timeout.as_mut() { + match Pin::new(timeout).poll(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(()) => {} } } - match self.stream.poll() { - Ok(item) => { + match Pin::new(&mut self.stream).poll_next(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(None) => { + if self.next_delay > self.initial_delay { + self.next_delay = self.initial_delay; + } + return Poll::Ready(None); + } + Poll::Ready(Some(Ok(item))) => { if self.next_delay > self.initial_delay { self.next_delay = self.initial_delay; } - return Ok(item); + + return Poll::Ready(Some(item)); } - Err(ref err) => { + Poll::Ready(Some(Err(ref err))) => { if connection_error(err) { warn!("Connection Error: {:?}", err); continue; @@ -76,7 +78,7 @@ where }; debug!("Error accepting connection: {}", err); debug!("The server will stop accepting connections for {:?}", self.next_delay); - self.timeout = Some(Delay::new(Instant::now() + self.next_delay)); + self.timeout = Some(tokio::time::delay_for(self.next_delay)); } } } diff --git a/stdio/Cargo.toml b/stdio/Cargo.toml index 72bda0643..8c907431f 100644 --- a/stdio/Cargo.toml +++ b/stdio/Cargo.toml @@ -7,18 +7,17 @@ homepage = "https://github.com/paritytech/jsonrpc" license = "MIT" name = "jsonrpc-stdio-server" repository = "https://github.com/paritytech/jsonrpc" -version = "14.2.0" +version = "17.1.0" [dependencies] -futures = "0.1.23" -jsonrpc-core = { version = "14.2", path = "../core" } +futures = "0.3" +jsonrpc-core = { version = "17.1", path = "../core" } log = "0.4" -tokio = "0.1.7" -tokio-codec = "0.1.0" -tokio-io = "0.1.7" -tokio-stdin-stdout = "0.1.4" +tokio = { version = "0.2", features = ["io-std", "io-driver", "io-util"] } +tokio-util = { version = "0.3", features = ["codec"] } [dev-dependencies] +tokio = { version = "0.2", features = ["rt-core", "macros"] } lazy_static = "1.0" env_logger = "0.7" diff --git a/stdio/README.md b/stdio/README.md index c2660dfef..bd8152526 100644 --- a/stdio/README.md +++ b/stdio/README.md @@ -10,7 +10,7 @@ Takes one request per line and outputs each response on a new line. ``` [dependencies] -jsonrpc-stdio-server = "14.2" +jsonrpc-stdio-server = "15.0" ``` `main.rs` diff --git a/stdio/examples/stdio.rs b/stdio/examples/stdio.rs index 5ba4ba31c..bd2bc2caa 100644 --- a/stdio/examples/stdio.rs +++ b/stdio/examples/stdio.rs @@ -1,9 +1,11 @@ use jsonrpc_stdio_server::jsonrpc_core::*; use jsonrpc_stdio_server::ServerBuilder; -fn main() { +#[tokio::main] +async fn main() { let mut io = IoHandler::default(); - io.add_method("say_hello", |_params| Ok(Value::String("hello".to_owned()))); + io.add_sync_method("say_hello", |_params| Ok(Value::String("hello".to_owned()))); - ServerBuilder::new(io).build(); + let server = ServerBuilder::new(io).build(); + server.await; } diff --git a/stdio/src/lib.rs b/stdio/src/lib.rs index 3982abc0a..79918c44a 100644 --- a/stdio/src/lib.rs +++ b/stdio/src/lib.rs @@ -5,74 +5,94 @@ //! use jsonrpc_stdio_server::ServerBuilder; //! use jsonrpc_stdio_server::jsonrpc_core::*; //! -//! fn main() { +//! #[tokio::main] +//! async fn main() { //! let mut io = IoHandler::default(); -//! io.add_method("say_hello", |_params| { +//! io.add_sync_method("say_hello", |_params| { //! Ok(Value::String("hello".to_owned())) //! }); //! -//! ServerBuilder::new(io).build(); +//! let server = ServerBuilder::new(io).build(); +//! server.await; //! } //! ``` #![deny(missing_docs)] -use tokio; -use tokio_stdin_stdout; +use std::future::Future; +use std::sync::Arc; + #[macro_use] extern crate log; pub use jsonrpc_core; +pub use tokio; -use jsonrpc_core::IoHandler; -use std::sync::Arc; -use tokio::prelude::{Future, Stream}; -use tokio_codec::{FramedRead, FramedWrite, LinesCodec}; +use jsonrpc_core::{MetaIoHandler, Metadata, Middleware}; +use tokio_util::codec::{FramedRead, LinesCodec}; /// Stdio server builder -pub struct ServerBuilder { - handler: Arc, +pub struct ServerBuilder = jsonrpc_core::NoopMiddleware> { + handler: Arc>, } -impl ServerBuilder { +impl> ServerBuilder +where + M: Default, + T::Future: Unpin, + T::CallFuture: Unpin, +{ /// Returns a new server instance - pub fn new(handler: T) -> Self - where - T: Into, - { + pub fn new(handler: impl Into>) -> Self { ServerBuilder { handler: Arc::new(handler.into()), } } + /// Returns a server future that needs to be polled in order to make progress. + /// /// Will block until EOF is read or until an error occurs. /// The server reads from STDIN line-by-line, one request is taken /// per line and each response is written to STDOUT on a new line. - pub fn build(&self) { - let stdin = tokio_stdin_stdout::stdin(0); - let stdout = tokio_stdin_stdout::stdout(0).make_sendable(); + pub fn build(&self) -> impl Future + 'static { + let handler = self.handler.clone(); - let framed_stdin = FramedRead::new(stdin, LinesCodec::new()); - let framed_stdout = FramedWrite::new(stdout, LinesCodec::new()); + async move { + let stdin = tokio::io::stdin(); + let mut stdout = tokio::io::stdout(); - let handler = self.handler.clone(); - let future = framed_stdin - .and_then(move |line| process(&handler, line).map_err(|_| unreachable!())) - .forward(framed_stdout) - .map(|_| ()) - .map_err(|e| panic!("{:?}", e)); + let mut framed_stdin = FramedRead::new(stdin, LinesCodec::new()); - tokio::run(future); + use futures::StreamExt; + while let Some(request) = framed_stdin.next().await { + match request { + Ok(line) => { + let res = Self::process(&handler, line).await; + let mut sanitized = res.replace('\n', ""); + sanitized.push('\n'); + use tokio::io::AsyncWriteExt; + if let Err(e) = stdout.write_all(sanitized.as_bytes()).await { + log::warn!("Error writing response: {:?}", e); + } + } + Err(e) => { + log::warn!("Error reading line: {:?}", e); + } + } + } + } } -} -/// Process a request asynchronously -fn process(io: &Arc, input: String) -> impl Future + Send { - io.handle_request(&input).map(move |result| match result { - Some(res) => res, - None => { - info!("JSON RPC request produced no response: {:?}", input); - String::from("") - } - }) + /// Process a request asynchronously + fn process(io: &Arc>, input: String) -> impl Future + Send { + use jsonrpc_core::futures::FutureExt; + let f = io.handle_request(&input, Default::default()); + f.map(move |result| match result { + Some(res) => res, + None => { + info!("JSON RPC request produced no response: {:?}", input); + String::from("") + } + }) + } } diff --git a/tcp/Cargo.toml b/tcp/Cargo.toml index 0a76fa921..a25777300 100644 --- a/tcp/Cargo.toml +++ b/tcp/Cargo.toml @@ -7,14 +7,14 @@ homepage = "https://github.com/paritytech/jsonrpc" license = "MIT" name = "jsonrpc-tcp-server" repository = "https://github.com/paritytech/jsonrpc" -version = "14.2.0" +version = "17.1.0" [dependencies] +jsonrpc-core = { version = "17.1", path = "../core" } +jsonrpc-server-utils = { version = "17.1", path = "../server-utils" } log = "0.4" -parking_lot = "0.10.0" -tokio-service = "0.1" -jsonrpc-core = { version = "14.2", path = "../core" } -jsonrpc-server-utils = { version = "14.2", path = "../server-utils" } +parking_lot = "0.11.0" +tower-service = "0.3" [dev-dependencies] lazy_static = "1.0" diff --git a/tcp/README.md b/tcp/README.md index 81fac2166..7e12cff57 100644 --- a/tcp/README.md +++ b/tcp/README.md @@ -9,7 +9,7 @@ TCP server for JSON-RPC 2.0. ``` [dependencies] -jsonrpc-tcp-server = "14.2" +jsonrpc-tcp-server = "15.0" ``` `main.rs` diff --git a/tcp/examples/tcp.rs b/tcp/examples/tcp.rs index 58570cb1f..0c51a5c17 100644 --- a/tcp/examples/tcp.rs +++ b/tcp/examples/tcp.rs @@ -5,7 +5,7 @@ use jsonrpc_tcp_server::ServerBuilder; fn main() { env_logger::init(); let mut io = IoHandler::default(); - io.add_method("say_hello", |_params| { + io.add_sync_method("say_hello", |_params| { println!("Processing"); Ok(Value::String("hello".to_owned())) }); diff --git a/tcp/src/dispatch.rs b/tcp/src/dispatch.rs index 8af9cc210..664e97a33 100644 --- a/tcp/src/dispatch.rs +++ b/tcp/src/dispatch.rs @@ -1,23 +1,23 @@ -use std; use std::collections::HashMap; use std::net::SocketAddr; +use std::pin::Pin; use std::sync::Arc; +use std::task::Poll; -use crate::jsonrpc::futures::sync::mpsc; -use crate::jsonrpc::futures::{Async, Future, Poll, Sink, Stream}; +use crate::futures::{channel::mpsc, Stream}; use parking_lot::Mutex; -pub type SenderChannels = Mutex>>; +pub type SenderChannels = Mutex>>; -pub struct PeerMessageQueue { +pub struct PeerMessageQueue { up: S, - receiver: Option>, + receiver: Option>, _addr: SocketAddr, } -impl PeerMessageQueue { - pub fn new(response_stream: S, receiver: mpsc::Receiver, addr: SocketAddr) -> Self { +impl PeerMessageQueue { + pub fn new(response_stream: S, receiver: mpsc::UnboundedReceiver, addr: SocketAddr) -> Self { PeerMessageQueue { up: response_stream, receiver: Some(receiver), @@ -32,11 +32,11 @@ pub enum PushMessageError { /// Invalid peer NoSuchPeer, /// Send error - Send(mpsc::SendError), + Send(mpsc::TrySendError), } -impl From> for PushMessageError { - fn from(send_err: mpsc::SendError) -> Self { +impl From> for PushMessageError { + fn from(send_err: mpsc::TrySendError) -> Self { PushMessageError::Send(send_err) } } @@ -59,8 +59,7 @@ impl Dispatcher { match channels.get_mut(peer_addr) { Some(channel) => { - // todo: maybe async here later? - channel.send(msg).wait().map_err(PushMessageError::from)?; + channel.unbounded_send(msg).map_err(PushMessageError::from)?; Ok(()) } None => Err(PushMessageError::NoSuchPeer), @@ -78,9 +77,8 @@ impl Dispatcher { } } -impl> Stream for PeerMessageQueue { - type Item = String; - type Error = std::io::Error; +impl> + Unpin> Stream for PeerMessageQueue { + type Item = std::io::Result; // The receiver will never return `Ok(Async::Ready(None))` // Because the sender is kept in `SenderChannels` and it will never be dropped until `the stream` is resolved. @@ -90,32 +88,32 @@ impl> Stream for PeerMessageQue // However, it is possible to have a race between `poll` and `push_work` if the connection is dropped. // Therefore, the receiver is then dropped when the connection is dropped and an error is propagated when // a `send` attempt is made on that channel. - fn poll(&mut self) -> Poll, std::io::Error> { + fn poll_next(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll> { // check if we have response pending + let this = Pin::into_inner(self); - let up_closed = match self.up.poll() { - Ok(Async::Ready(Some(item))) => return Ok(Async::Ready(Some(item))), - Ok(Async::Ready(None)) => true, - Ok(Async::NotReady) => false, - err => return err, + let up_closed = match Pin::new(&mut this.up).poll_next(cx) { + Poll::Ready(Some(Ok(item))) => return Poll::Ready(Some(Ok(item))), + Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))), + Poll::Ready(None) => true, + Poll::Pending => false, }; - let rx = match &mut self.receiver { + let mut rx = match &mut this.receiver { None => { debug_assert!(up_closed); - return Ok(Async::Ready(None)); + return Poll::Ready(None); } Some(rx) => rx, }; - match rx.poll() { - Ok(Async::Ready(Some(item))) => Ok(Async::Ready(Some(item))), - Ok(Async::Ready(None)) | Ok(Async::NotReady) if up_closed => { - self.receiver = None; - Ok(Async::Ready(None)) + match Pin::new(&mut rx).poll_next(cx) { + Poll::Ready(Some(item)) => Poll::Ready(Some(Ok(item))), + Poll::Ready(None) | Poll::Pending if up_closed => { + this.receiver = None; + Poll::Ready(None) } - Ok(Async::Ready(None)) | Ok(Async::NotReady) => Ok(Async::NotReady), - Err(_) => Err(std::io::Error::new(std::io::ErrorKind::Other, "MPSC error")), + Poll::Ready(None) | Poll::Pending => Poll::Pending, } } } diff --git a/tcp/src/lib.rs b/tcp/src/lib.rs index 3c0c1c847..b78e4be54 100644 --- a/tcp/src/lib.rs +++ b/tcp/src/lib.rs @@ -6,7 +6,7 @@ //! //! fn main() { //! let mut io = IoHandler::default(); -//! io.add_method("say_hello", |_params| { +//! io.add_sync_method("say_hello", |_params| { //! Ok(Value::String("hello".to_string())) //! }); //! let server = ServerBuilder::new(io) @@ -42,6 +42,8 @@ mod tests; use jsonrpc_core as jsonrpc; +pub(crate) use crate::jsonrpc::futures; + pub use self::server_utils::{codecs::Separator, tokio}; pub use crate::dispatch::{Dispatcher, PushMessageError}; pub use crate::meta::{MetaExtractor, RequestContext}; diff --git a/tcp/src/meta.rs b/tcp/src/meta.rs index c057e733c..7cf2d4586 100644 --- a/tcp/src/meta.rs +++ b/tcp/src/meta.rs @@ -1,14 +1,13 @@ use std::net::SocketAddr; -use crate::jsonrpc::futures::sync::mpsc; -use crate::jsonrpc::Metadata; +use crate::jsonrpc::{futures::channel::mpsc, Metadata}; /// Request context pub struct RequestContext { /// Peer Address pub peer_addr: SocketAddr, /// Peer Sender channel - pub sender: mpsc::Sender, + pub sender: mpsc::UnboundedSender, } /// Metadata extractor (per session) diff --git a/tcp/src/server.rs b/tcp/src/server.rs index 9af74d038..55a2b8d4a 100644 --- a/tcp/src/server.rs +++ b/tcp/src/server.rs @@ -1,13 +1,13 @@ -use std; +use std::io; use std::net::SocketAddr; +use std::pin::Pin; use std::sync::Arc; -use tokio_service::Service as TokioService; +use tower_service::Service as _; -use crate::jsonrpc::futures::sync::{mpsc, oneshot}; -use crate::jsonrpc::futures::{future, Future, Sink, Stream}; +use crate::futures::{self, future}; use crate::jsonrpc::{middleware, MetaIoHandler, Metadata, Middleware}; -use crate::server_utils::{codecs, reactor, tokio, tokio_codec::Framed, SuspendableStream}; +use crate::server_utils::{codecs, reactor, tokio, tokio_util::codec::Framed, SuspendableStream}; use crate::dispatch::{Dispatcher, PeerMessageQueue, SenderChannels}; use crate::meta::{MetaExtractor, NoopExtractor, RequestContext}; @@ -23,7 +23,11 @@ pub struct ServerBuilder = middleware::Noop> outgoing_separator: codecs::Separator, } -impl + 'static> ServerBuilder { +impl + 'static> ServerBuilder +where + S::Future: Unpin, + S::CallFuture: Unpin, +{ /// Creates new `ServerBuilder` wih given `IoHandler` pub fn new(handler: T) -> Self where @@ -33,7 +37,11 @@ impl + 'static> ServerBuilder { } } -impl + 'static> ServerBuilder { +impl + 'static> ServerBuilder +where + S::Future: Unpin, + S::CallFuture: Unpin, +{ /// Creates new `ServerBuilder` wih given `IoHandler` pub fn with_meta_extractor(handler: T, extractor: E) -> Self where @@ -51,7 +59,7 @@ impl + 'static> ServerBuilder { } /// Utilize existing event loop executor. - pub fn event_loop_executor(mut self, handle: tokio::runtime::TaskExecutor) -> Self { + pub fn event_loop_executor(mut self, handle: reactor::TaskExecutor) -> Self { self.executor = reactor::UninitializedExecutor::Shared(handle); self } @@ -70,7 +78,7 @@ impl + 'static> ServerBuilder { } /// Starts a new server - pub fn start(self, addr: &SocketAddr) -> std::io::Result { + pub fn start(self, addr: &SocketAddr) -> io::Result { let meta_extractor = self.meta_extractor.clone(); let rpc_handler = self.handler.clone(); let channels = self.channels.clone(); @@ -78,25 +86,26 @@ impl + 'static> ServerBuilder { let outgoing_separator = self.outgoing_separator; let address = addr.to_owned(); let (tx, rx) = std::sync::mpsc::channel(); - let (stop_tx, stop_rx) = oneshot::channel(); + let (stop_tx, stop_rx) = futures::channel::oneshot::channel(); let executor = self.executor.initialize()?; - executor.spawn(future::lazy(move || { - let start = move || { - let listener = tokio::net::TcpListener::bind(&address)?; - let connections = SuspendableStream::new(listener.incoming()); + use futures::{FutureExt, SinkExt, StreamExt, TryStreamExt}; + executor.executor().spawn(async move { + let start = async { + let listener = tokio::net::TcpListener::bind(&address).await?; + let connections = SuspendableStream::new(listener); - let server = connections.map(move |socket| { + let server = connections.map(|socket| { let peer_addr = match socket.peer_addr() { Ok(addr) => addr, Err(e) => { warn!(target: "tcp", "Unable to determine socket peer address, ignoring connection {}", e); - return future::Either::A(future::ok(())); + return future::Either::Left(async { io::Result::Ok(()) }); } }; trace!(target: "tcp", "Accepted incoming connection from {}", &peer_addr); - let (sender, receiver) = mpsc::channel(65536); + let (sender, receiver) = futures::channel::mpsc::unbounded(); let context = RequestContext { peer_addr, @@ -104,31 +113,33 @@ impl + 'static> ServerBuilder { }; let meta = meta_extractor.extract(&context); - let service = Service::new(peer_addr, rpc_handler.clone(), meta); - let (writer, reader) = Framed::new( + let mut service = Service::new(peer_addr, rpc_handler.clone(), meta); + let (mut writer, reader) = Framed::new( socket, codecs::StreamCodec::new(incoming_separator.clone(), outgoing_separator.clone()), ) .split(); - let responses = reader.and_then(move |req| { - service.call(req).then(|response| match response { - Err(e) => { - warn!(target: "tcp", "Error while processing request: {:?}", e); - future::ok(String::new()) - } - Ok(None) => { - trace!(target: "tcp", "JSON RPC request produced no response"); - future::ok(String::new()) - } - Ok(Some(response_data)) => { - trace!(target: "tcp", "Sent response: {}", &response_data); - future::ok(response_data) - } - }) - }); - - let peer_message_queue = { + // Work around https://github.com/rust-lang/rust/issues/64552 by boxing the stream type + let responses: Pin> + Send>> = + Box::pin(reader.and_then(move |req| { + service.call(req).then(|response| match response { + Err(e) => { + warn!(target: "tcp", "Error while processing request: {:?}", e); + future::ok(String::new()) + } + Ok(None) => { + trace!(target: "tcp", "JSON RPC request produced no response"); + future::ok(String::new()) + } + Ok(Some(response_data)) => { + trace!(target: "tcp", "Sent response: {}", &response_data); + future::ok(response_data) + } + }) + })); + + let mut peer_message_queue = { let mut channels = channels.lock(); channels.insert(peer_addr, sender.clone()); @@ -136,42 +147,33 @@ impl + 'static> ServerBuilder { }; let shared_channels = channels.clone(); - let writer = writer.send_all(peer_message_queue).then(move |_| { + let writer = async move { + writer.send_all(&mut peer_message_queue).await?; trace!(target: "tcp", "Peer {}: service finished", peer_addr); let mut channels = shared_channels.lock(); channels.remove(&peer_addr); Ok(()) - }); + }; - future::Either::B(writer) + future::Either::Right(writer) }); Ok(server) }; - let stop = stop_rx.map_err(|_| ()); - match start() { + match start.await { Ok(server) => { tx.send(Ok(())).expect("Rx is blocking parent thread."); - future::Either::A( - server - .buffer_unordered(1024) - .for_each(|_| Ok(())) - .select(stop) - .map(|_| ()) - .map_err(|(e, _)| { - error!("Error while executing the server: {:?}", e); - }), - ) + let server = server.buffer_unordered(1024).for_each(|_| async { () }); + + future::select(Box::pin(server), stop_rx).await; } Err(e) => { tx.send(Err(e)).expect("Rx is blocking parent thread."); - future::Either::B(stop.map_err(|e| { - error!("Error while executing the server: {:?}", e); - })) + let _ = stop_rx.await; } } - })); + }); let res = rx.recv().expect("Response is always sent before tx is dropped."); @@ -190,7 +192,7 @@ impl + 'static> ServerBuilder { /// TCP Server handle pub struct Server { executor: Option, - stop: Option>, + stop: Option>, } impl Server { diff --git a/tcp/src/service.rs b/tcp/src/service.rs index 9254981fc..558b2feba 100644 --- a/tcp/src/service.rs +++ b/tcp/src/service.rs @@ -1,9 +1,11 @@ +use std::future::Future; use std::net::SocketAddr; +use std::pin::Pin; use std::sync::Arc; +use std::task::{Context, Poll}; -use tokio_service; - -use crate::jsonrpc::{middleware, FutureResult, MetaIoHandler, Metadata, Middleware}; +use crate::futures; +use crate::jsonrpc::{middleware, MetaIoHandler, Metadata, Middleware}; pub struct Service = middleware::Noop> { handler: Arc>, @@ -21,20 +23,27 @@ impl> Service { } } -impl> tokio_service::Service for Service { +impl> tower_service::Service for Service +where + S::Future: Unpin, + S::CallFuture: Unpin, +{ // These types must match the corresponding protocol types: - type Request = String; type Response = Option; - // For non-streaming protocols, service errors are always io::Error type Error = (); // The future for computing the response; box it for simplicity. - type Future = FutureResult; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } // Produce a future for computing a response from a request. - fn call(&self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: String) -> Self::Future { + use futures::FutureExt; trace!(target: "tcp", "Accepted request from peer {}: {}", &self.peer_addr, req); - self.handler.handle_request(&req, self.meta.clone()) + Box::pin(self.handler.handle_request(&req, self.meta.clone()).map(Ok)) } } diff --git a/tcp/src/tests.rs b/tcp/src/tests.rs index 71b819646..b95e1b288 100644 --- a/tcp/src/tests.rs +++ b/tcp/src/tests.rs @@ -1,12 +1,14 @@ use std::net::{Shutdown, SocketAddr}; use std::str::FromStr; use std::sync::Arc; -use std::time::{Duration, Instant}; +use std::time::Duration; -use crate::jsonrpc::futures::{self, future, Future}; -use crate::jsonrpc::{MetaIoHandler, Metadata, Value}; +use jsonrpc_core::{MetaIoHandler, Metadata, Value}; +use tokio::io::AsyncReadExt; +use tokio::io::AsyncWriteExt; -use crate::server_utils::tokio::{self, io, net::TcpStream, timer::Delay}; +use crate::futures; +use crate::server_utils::tokio::{self, net::TcpStream}; use parking_lot::Mutex; @@ -16,16 +18,21 @@ use crate::ServerBuilder; fn casual_server() -> ServerBuilder { let mut io = MetaIoHandler::<()>::default(); - io.add_method("say_hello", |_params| Ok(Value::String("hello".to_string()))); + io.add_sync_method("say_hello", |_params| Ok(Value::String("hello".to_string()))); ServerBuilder::new(io) } +fn run_future(fut: impl std::future::Future + Send) -> O { + let mut rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(fut) +} + #[test] fn doc_test() { crate::logger::init_log(); let mut io = MetaIoHandler::<()>::default(); - io.add_method("say_hello", |_params| Ok(Value::String("hello".to_string()))); + io.add_sync_method("say_hello", |_params| Ok(Value::String("hello".to_string()))); let server = ServerBuilder::new(io); server @@ -41,11 +48,7 @@ fn doc_test_connect() { let server = casual_server(); let _server = server.start(&addr).expect("Server must run with no issues"); - let stream = TcpStream::connect(&addr) - .and_then(move |_stream| Ok(())) - .map_err(|err| panic!("Server connection error: {:?}", err)); - - tokio::run(stream); + run_future(async move { TcpStream::connect(&addr).await }).expect("Server connection error"); } #[test] @@ -56,14 +59,11 @@ fn disconnect() { let dispatcher = server.dispatcher(); let _server = server.start(&addr).expect("Server must run with no issues"); - let stream = TcpStream::connect(&addr) - .and_then(move |stream| { - assert_eq!(stream.peer_addr().unwrap(), addr); - stream.shutdown(::std::net::Shutdown::Both) - }) - .map_err(|err| panic!("Error disconnecting: {:?}", err)); - - tokio::run(stream); + run_future(async move { + let stream = TcpStream::connect(&addr).await.unwrap(); + assert_eq!(stream.peer_addr().unwrap(), addr); + stream.shutdown(::std::net::Shutdown::Both).unwrap(); + }); ::std::thread::sleep(::std::time::Duration::from_millis(50)); @@ -71,19 +71,22 @@ fn disconnect() { } fn dummy_request(addr: &SocketAddr, data: Vec) -> Vec { - let (ret_tx, ret_rx) = futures::sync::oneshot::channel(); - - let stream = TcpStream::connect(addr) - .and_then(move |stream| io::write_all(stream, data)) - .and_then(|(stream, _data)| { - stream.shutdown(Shutdown::Write).unwrap(); - io::read_to_end(stream, vec![]) - }) - .and_then(move |(_stream, read_buf)| ret_tx.send(read_buf).map_err(|err| panic!("Unable to send {:?}", err))) - .map_err(|err| panic!("Error connecting or closing connection: {:?}", err)); - - tokio::run(stream); - ret_rx.wait().expect("Unable to receive result") + let (ret_tx, ret_rx) = std::sync::mpsc::channel(); + + let stream = async move { + let mut stream = TcpStream::connect(addr).await?; + stream.write_all(&data).await?; + stream.shutdown(Shutdown::Write)?; + let mut read_buf = vec![]; + let _ = stream.read_to_end(&mut read_buf).await; + + let _ = ret_tx.send(read_buf).map_err(|err| panic!("Unable to send {:?}", err)); + + Ok::<(), Box>(()) + }; + + run_future(stream).unwrap(); + ret_rx.recv().expect("Unable to receive result") } fn dummy_request_str(addr: &SocketAddr, data: Vec) -> String { @@ -180,7 +183,7 @@ impl MetaExtractor for PeerMetaExtractor { fn meta_server() -> ServerBuilder { let mut io = MetaIoHandler::::default(); io.add_method_with_meta("say_hello", |_params, meta: SocketMetadata| { - future::ok(Value::String(format!("hello, {}", meta.addr()))) + jsonrpc_core::futures::future::ready(Ok(Value::String(format!("hello, {}", meta.addr())))) }); ServerBuilder::new(io).session_meta_extractor(PeerMetaExtractor) } @@ -223,7 +226,7 @@ fn message() { let addr: SocketAddr = "127.0.0.1:17790".parse().unwrap(); let mut io = MetaIoHandler::::default(); io.add_method_with_meta("say_hello", |_params, _: SocketMetadata| { - future::ok(Value::String("hello".to_owned())) + jsonrpc_core::futures::future::ready(Ok(Value::String("hello".to_owned()))) }); let extractor = PeerListMetaExtractor::default(); let peer_list = extractor.peers.clone(); @@ -232,67 +235,62 @@ fn message() { let _server = server.start(&addr).expect("Server must run with no issues"); - let delay = Delay::new(Instant::now() + Duration::from_millis(500)).map_err(|err| panic!("{:?}", err)); - let message = "ping"; let executed_dispatch = Arc::new(Mutex::new(false)); let executed_request = Arc::new(Mutex::new(false)); let executed_dispatch_move = executed_dispatch.clone(); let executed_request_move = executed_request.clone(); - // CLIENT RUN - let stream = TcpStream::connect(&addr) - .and_then(|stream| future::ok(stream).join(delay)) - .and_then(move |stream| { - let peer_addr = peer_list.lock()[0].clone(); - dispatcher - .push_message(&peer_addr, message.to_owned()) - .expect("Should be sent with no errors"); - trace!(target: "tcp", "Dispatched message for {}", peer_addr); - future::ok(stream) - }) - .and_then(move |(stream, _)| { - // Read message plus newline appended by codec. - io::read_exact(stream, vec![0u8; message.len() + 1]) - }) - .and_then(move |(stream, read_buf)| { - trace!(target: "tcp", "Read ping message"); - let ping_signal = read_buf[..].to_vec(); - - assert_eq!( - format!("{}\n", message), - String::from_utf8(ping_signal).expect("String should be utf-8"), - "Sent request does not match received by the peer", - ); - // ensure that the above assert was actually triggered - *executed_dispatch_move.lock() = true; - - future::ok(stream) - }) - .and_then(|stream| { - // make request AFTER message dispatches - let data = b"{\"jsonrpc\": \"2.0\", \"method\": \"say_hello\", \"params\": [42, 23], \"id\": 1}\n"; - io::write_all(stream, &data[..]) - }) - .and_then(|(stream, _)| { - stream.shutdown(Shutdown::Write).unwrap(); - io::read_to_end(stream, Vec::new()) - }) - .and_then(move |(_, read_buf)| { - trace!(target: "tcp", "Read response message"); - let response_signal = read_buf[..].to_vec(); - assert_eq!( - "{\"jsonrpc\":\"2.0\",\"result\":\"hello\",\"id\":1}\n", - String::from_utf8(response_signal).expect("String should be utf-8"), - "Response does not match the expected handling", - ); - *executed_request_move.lock() = true; - - future::ok(()) - }) - .map_err(|err| panic!("Dispach message error: {:?}", err)); - - tokio::run(stream); + let client = async move { + let stream = TcpStream::connect(&addr); + let delay = tokio::time::delay_for(Duration::from_millis(500)); + let (stream, _) = futures::join!(stream, delay); + let mut stream = stream?; + + let peer_addr = peer_list.lock()[0].clone(); + dispatcher + .push_message(&peer_addr, message.to_owned()) + .expect("Should be sent with no errors"); + trace!(target: "tcp", "Dispatched message for {}", peer_addr); + + // Read message plus newline appended by codec. + let mut read_buf = vec![0u8; message.len() + 1]; + let _ = stream.read_exact(&mut read_buf).await?; + + trace!(target: "tcp", "Read ping message"); + let ping_signal = read_buf[..].to_vec(); + + assert_eq!( + format!("{}\n", message), + String::from_utf8(ping_signal).expect("String should be utf-8"), + "Sent request does not match received by the peer", + ); + // ensure that the above assert was actually triggered + *executed_dispatch_move.lock() = true; + + // make request AFTER message dispatches + let data = b"{\"jsonrpc\": \"2.0\", \"method\": \"say_hello\", \"params\": [42, 23], \"id\": 1}\n"; + stream.write_all(&data[..]).await?; + + stream.shutdown(Shutdown::Write).unwrap(); + let mut read_buf = vec![]; + let _ = stream.read_to_end(&mut read_buf).await?; + + trace!(target: "tcp", "Read response message"); + let response_signal = read_buf[..].to_vec(); + assert_eq!( + "{\"jsonrpc\":\"2.0\",\"result\":\"hello\",\"id\":1}\n", + String::from_utf8(response_signal).expect("String should be utf-8"), + "Response does not match the expected handling", + ); + *executed_request_move.lock() = true; + + // delay + Ok::<(), Box>(()) + }; + + run_future(client).unwrap(); + assert!(*executed_dispatch.lock()); assert!(*executed_request.lock()); } diff --git a/test/Cargo.toml b/test/Cargo.toml index 679ba3953..bfe2c061f 100644 --- a/test/Cargo.toml +++ b/test/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "jsonrpc-test" description = "Simple test framework for JSON-RPC." -version = "14.2.0" +version = "17.1.0" authors = ["Tomasz DrwiÄ™ga "] license = "MIT" homepage = "https://github.com/paritytech/jsonrpc" @@ -10,9 +10,9 @@ documentation = "https://docs.rs/jsonrpc-test/" edition = "2018" [dependencies] -jsonrpc-core = { version = "14.2", path = "../core" } -jsonrpc-core-client = { version = "14.2", path = "../core-client" } -jsonrpc-pubsub = { version = "14.2", path = "../pubsub" } +jsonrpc-core = { version = "17.1", path = "../core" } +jsonrpc-core-client = { version = "17.1", path = "../core-client" } +jsonrpc-pubsub = { version = "17.1", path = "../pubsub" } log = "0.4" serde = "1.0" serde_json = "1.0" @@ -21,5 +21,5 @@ serde_json = "1.0" arbitrary_precision = ["jsonrpc-core-client/arbitrary_precision", "serde_json/arbitrary_precision", "jsonrpc-core/arbitrary_precision"] [dev-dependencies] -jsonrpc-derive = { version = "14.2", path = "../derive" } +jsonrpc-derive = { version = "17.1", path = "../derive" } diff --git a/test/src/lib.rs b/test/src/lib.rs index 985709fdb..5cc963ab9 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -30,7 +30,7 @@ //! // You can also test RPC created without macros: //! let rpc = { //! let mut io = IoHandler::new(); -//! io.add_method("rpc_test_method", |_| { +//! io.add_sync_method("rpc_test_method", |_| { //! Err(Error::internal_error()) //! }); //! test::Rpc::from(io) @@ -147,7 +147,7 @@ mod tests { // given let rpc = { let mut io = rpc::IoHandler::new(); - io.add_method("test_method", |_| Ok(rpc::Value::Array(vec![5.into(), 10.into()]))); + io.add_sync_method("test_method", |_| Ok(rpc::Value::Array(vec![5.into(), 10.into()]))); Rpc::from(io) }; @@ -160,7 +160,7 @@ mod tests { // given let rpc = { let mut io = rpc::IoHandler::new(); - io.add_method("test_method", |_| Ok(rpc::Value::Array(vec![5.into(), 10.into()]))); + io.add_sync_method("test_method", |_| Ok(rpc::Value::Array(vec![5.into(), 10.into()]))); Rpc::from(io) }; diff --git a/ws/Cargo.toml b/ws/Cargo.toml index 0c9e0e627..a03be3f8c 100644 --- a/ws/Cargo.toml +++ b/ws/Cargo.toml @@ -7,15 +7,16 @@ homepage = "https://github.com/paritytech/jsonrpc" license = "MIT" name = "jsonrpc-ws-server" repository = "https://github.com/paritytech/jsonrpc" -version = "14.2.0" +version = "17.1.0" [dependencies] -jsonrpc-core = { version = "14.2", path = "../core" } -jsonrpc-server-utils = { version = "14.2", path = "../server-utils" } +futures = "0.3" +jsonrpc-core = { version = "17.1", path = "../core" } +jsonrpc-server-utils = { version = "17.1", path = "../server-utils" } log = "0.4" -parking_lot = "0.10.0" +parking_lot = "0.11.0" slab = "0.4" -ws = "0.9" +parity-ws = "0.10" [badges] travis-ci = { repository = "paritytech/jsonrpc", branch = "master"} diff --git a/ws/README.md b/ws/README.md index 948056bc7..fed02adcd 100644 --- a/ws/README.md +++ b/ws/README.md @@ -9,7 +9,7 @@ WebSockets server for JSON-RPC 2.0. ``` [dependencies] -jsonrpc-ws-server = "14.2" +jsonrpc-ws-server = "15.0" ``` `main.rs` diff --git a/ws/examples/ws.rs b/ws/examples/ws.rs index ea6a9087f..69fd9a494 100644 --- a/ws/examples/ws.rs +++ b/ws/examples/ws.rs @@ -3,7 +3,7 @@ use jsonrpc_ws_server::ServerBuilder; fn main() { let mut io = IoHandler::default(); - io.add_method("say_hello", |_params| { + io.add_sync_method("say_hello", |_params| { println!("Processing"); Ok(Value::String("hello".to_owned())) }); diff --git a/ws/src/lib.rs b/ws/src/lib.rs index aeae08a5d..5c1cfc3ca 100644 --- a/ws/src/lib.rs +++ b/ws/src/lib.rs @@ -5,7 +5,7 @@ use jsonrpc_server_utils as server_utils; pub use jsonrpc_core; -pub use ws; +pub use parity_ws as ws; #[macro_use] extern crate log; diff --git a/ws/src/metadata.rs b/ws/src/metadata.rs index 07befcfe9..25b1bed82 100644 --- a/ws/src/metadata.rs +++ b/ws/src/metadata.rs @@ -1,9 +1,12 @@ use std::fmt; +use std::future::Future; +use std::pin::Pin; use std::sync::{atomic, Arc}; +use std::task::{Context, Poll}; -use crate::core::futures::sync::mpsc; -use crate::core::{self, futures}; -use crate::server_utils::{session, tokio::runtime::TaskExecutor}; +use crate::core; +use crate::core::futures::channel::mpsc; +use crate::server_utils::{reactor::TaskExecutor, session}; use crate::ws; use crate::error; @@ -78,10 +81,10 @@ pub struct RequestContext { impl RequestContext { /// Get this session as a `Sink` spawning a new future /// in the underlying event loop. - pub fn sender(&self) -> mpsc::Sender { + pub fn sender(&self) -> mpsc::UnboundedSender { let out = self.out.clone(); - let (sender, receiver) = mpsc::channel(1); - self.executor.spawn(SenderFuture(out, receiver)); + let (sender, receiver) = mpsc::unbounded(); + self.executor.spawn(SenderFuture(out, Box::new(receiver))); sender } } @@ -121,27 +124,23 @@ impl MetaExtractor for NoopExtractor { } } -struct SenderFuture(Sender, mpsc::Receiver); -impl futures::Future for SenderFuture { - type Item = (); - type Error = (); +struct SenderFuture(Sender, Box + Send + Unpin>); - fn poll(&mut self) -> futures::Poll { - use self::futures::Stream; +impl Future for SenderFuture { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + use futures::Stream; + + let this = Pin::into_inner(self); loop { - let item = self.1.poll()?; - match item { - futures::Async::NotReady => { - return Ok(futures::Async::NotReady); - } - futures::Async::Ready(None) => { - return Ok(futures::Async::Ready(())); - } - futures::Async::Ready(Some(val)) => { - if let Err(e) = self.0.send(val) { + match Pin::new(&mut this.1).poll_next(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(None) => return Poll::Ready(()), + Poll::Ready(Some(val)) => { + if let Err(e) = this.0.send(val) { warn!("Error sending a subscription update: {:?}", e); - return Ok(futures::Async::Ready(())); + return Poll::Ready(()); } } } diff --git a/ws/src/server.rs b/ws/src/server.rs index 4fb83d67a..55717dc7e 100644 --- a/ws/src/server.rs +++ b/ws/src/server.rs @@ -58,12 +58,20 @@ impl Server { executor: UninitializedExecutor, max_connections: usize, max_payload_bytes: usize, - ) -> Result { + max_in_buffer_capacity: usize, + max_out_buffer_capacity: usize, + ) -> Result + where + S::Future: Unpin, + S::CallFuture: Unpin, + { let config = { let mut config = ws::Settings::default(); config.max_connections = max_connections; // don't accept super large requests config.max_fragment_size = max_payload_bytes; + config.max_in_buffer_capacity = max_in_buffer_capacity; + config.max_out_buffer_capacity = max_out_buffer_capacity; // don't grow non-final fragments (to prevent DOS) config.fragments_grow = false; config.fragments_capacity = cmp::max(1, max_payload_bytes / config.fragment_size); diff --git a/ws/src/server_builder.rs b/ws/src/server_builder.rs index 1af8e6763..2bfdcaadc 100644 --- a/ws/src/server_builder.rs +++ b/ws/src/server_builder.rs @@ -2,10 +2,9 @@ use std::net::SocketAddr; use std::sync::Arc; use crate::core; -use crate::server_utils; use crate::server_utils::cors::Origin; use crate::server_utils::hosts::{DomainsValidation, Host}; -use crate::server_utils::reactor::UninitializedExecutor; +use crate::server_utils::reactor::{self, UninitializedExecutor}; use crate::server_utils::session::SessionStats; use crate::error::Result; @@ -24,9 +23,15 @@ pub struct ServerBuilder> { executor: UninitializedExecutor, max_connections: usize, max_payload_bytes: usize, + max_in_buffer_capacity: usize, + max_out_buffer_capacity: usize, } -impl> ServerBuilder { +impl> ServerBuilder +where + S::Future: Unpin, + S::CallFuture: Unpin, +{ /// Creates new `ServerBuilder` pub fn new(handler: T) -> Self where @@ -36,7 +41,11 @@ impl> ServerBuilder { } } -impl> ServerBuilder { +impl> ServerBuilder +where + S::Future: Unpin, + S::CallFuture: Unpin, +{ /// Creates new `ServerBuilder` pub fn with_meta_extractor(handler: T, extractor: E) -> Self where @@ -53,11 +62,13 @@ impl> ServerBuilder { executor: UninitializedExecutor::Unspawned, max_connections: 100, max_payload_bytes: 5 * 1024 * 1024, + max_in_buffer_capacity: 10 * 1024 * 1024, + max_out_buffer_capacity: 10 * 1024 * 1024, } } /// Utilize existing event loop executor to poll RPC results. - pub fn event_loop_executor(mut self, executor: server_utils::tokio::runtime::TaskExecutor) -> Self { + pub fn event_loop_executor(mut self, executor: reactor::TaskExecutor) -> Self { self.executor = UninitializedExecutor::Shared(executor); self } @@ -107,6 +118,20 @@ impl> ServerBuilder { self } + /// The maximum size to which the incoming buffer can grow. + /// Default: 10,485,760 + pub fn max_in_buffer_capacity(mut self, max_in_buffer_capacity: usize) -> Self { + self.max_in_buffer_capacity = max_in_buffer_capacity; + self + } + + /// The maximum size to which the outgoing buffer can grow. + /// Default: 10,485,760 + pub fn max_out_buffer_capacity(mut self, max_out_buffer_capacity: usize) -> Self { + self.max_out_buffer_capacity = max_out_buffer_capacity; + self + } + /// Starts a new `WebSocket` server in separate thread. /// Returns a `Server` handle which closes the server when droped. pub fn start(self, addr: &SocketAddr) -> Result { @@ -121,6 +146,51 @@ impl> ServerBuilder { self.executor, self.max_connections, self.max_payload_bytes, + self.max_in_buffer_capacity, + self.max_out_buffer_capacity, ) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn basic_server_builder() -> ServerBuilder<(), jsonrpc_core::middleware::Noop> { + let io = core::IoHandler::default(); + ServerBuilder::new(io) + } + #[test] + fn config_usize_vals_have_correct_defaults() { + let server = basic_server_builder(); + + assert_eq!(server.max_connections, 100); + assert_eq!(server.max_payload_bytes, 5 * 1024 * 1024); + assert_eq!(server.max_in_buffer_capacity, 10 * 1024 * 1024); + assert_eq!(server.max_out_buffer_capacity, 10 * 1024 * 1024); + } + + #[test] + fn config_usize_vals_can_be_set() { + let server = basic_server_builder(); + + // We can set them individually + let server = server.max_connections(10); + assert_eq!(server.max_connections, 10); + + let server = server.max_payload(29); + assert_eq!(server.max_payload_bytes, 29); + + let server = server.max_in_buffer_capacity(38); + assert_eq!(server.max_in_buffer_capacity, 38); + + let server = server.max_out_buffer_capacity(47); + assert_eq!(server.max_out_buffer_capacity, 47); + + // Setting values consecutively does not impact other values + assert_eq!(server.max_connections, 10); + assert_eq!(server.max_payload_bytes, 29); + assert_eq!(server.max_in_buffer_capacity, 38); + assert_eq!(server.max_out_buffer_capacity, 47); + } +} diff --git a/ws/src/session.rs b/ws/src/session.rs index 63c40acb8..eef7b0e9e 100644 --- a/ws/src/session.rs +++ b/ws/src/session.rs @@ -1,17 +1,20 @@ -use std; +use std::future::Future; +use std::pin::Pin; use std::sync::{atomic, Arc}; +use std::task::{Context, Poll}; use crate::core; -use crate::core::futures::sync::oneshot; -use crate::core::futures::{Async, Future, Poll}; +use futures::channel::oneshot; +use futures::future; +use futures::FutureExt; use parking_lot::Mutex; use slab::Slab; use crate::server_utils::cors::Origin; use crate::server_utils::hosts::Host; +use crate::server_utils::reactor::TaskExecutor; use crate::server_utils::session::{SessionId, SessionStats}; -use crate::server_utils::tokio::runtime::TaskExecutor; use crate::server_utils::Pattern; use crate::ws; @@ -123,16 +126,16 @@ impl LivenessPoll { } impl Future for LivenessPoll { - type Item = (); - type Error = (); + type Output = (); - fn poll(&mut self) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = Pin::into_inner(self); // if the future resolves ok then we've been signalled to return. // it should never be cancelled, but if it was the session definitely // isn't live. - match self.rx.poll() { - Ok(Async::Ready(_)) | Err(_) => Ok(Async::Ready(())), - Ok(Async::NotReady) => Ok(Async::NotReady), + match Pin::new(&mut this.rx).poll(cx) { + Poll::Ready(_) => Poll::Ready(()), + Poll::Pending => Poll::Pending, } } } @@ -205,7 +208,11 @@ impl> Session { } } -impl> ws::Handler for Session { +impl> ws::Handler for Session +where + S::Future: Unpin, + S::CallFuture: Unpin, +{ fn on_request(&mut self, req: &ws::Request) -> ws::Result { // Run middleware let action = if let Some(ref middleware) = self.request_middleware { @@ -264,30 +271,27 @@ impl> ws::Handler for Session { let poll_liveness = LivenessPoll::create(self.task_slab.clone()); let active_lock = self.active.clone(); - let future = self - .handler - .handle_request(req, metadata) - .map(move |response| { - if !active_lock.load(atomic::Ordering::SeqCst) { - return; - } - if let Some(result) = response { - let res = out.send(result); - match res { - Err(error::Error::ConnectionClosed) => { - active_lock.store(false, atomic::Ordering::SeqCst); - } - Err(e) => { - warn!("Error while sending response: {:?}", e); - } - _ => {} + let response = self.handler.handle_request(req, metadata); + + let future = response.map(move |response| { + if !active_lock.load(atomic::Ordering::SeqCst) { + return; + } + if let Some(result) = response { + let res = out.send(result); + match res { + Err(error::Error::ConnectionClosed) => { + active_lock.store(false, atomic::Ordering::SeqCst); } + Err(e) => { + warn!("Error while sending response: {:?}", e); + } + _ => {} } - }) - .select(poll_liveness) - .map(|_| ()) - .map_err(|_| ()); + } + }); + let future = future::select(future, poll_liveness); self.executor.spawn(future); Ok(()) @@ -328,7 +332,11 @@ impl> Factory { } } -impl> ws::Factory for Factory { +impl> ws::Factory for Factory +where + S::Future: Unpin, + S::CallFuture: Unpin, +{ type Handler = Session; fn connection_made(&mut self, sender: ws::Sender) -> Self::Handler { diff --git a/ws/src/tests.rs b/ws/src/tests.rs index d46e978f7..0d2928460 100644 --- a/ws/src/tests.rs +++ b/ws/src/tests.rs @@ -7,7 +7,6 @@ use std::thread; use std::time::Duration; use crate::core; -use crate::core::futures::Future; use crate::server_utils::hosts::DomainsValidation; use crate::ws; @@ -61,28 +60,27 @@ fn request(server: Server, request: &str) -> Response { } fn serve(port: u16) -> (Server, Arc) { - use crate::core::futures::sync::oneshot; - + use futures::{channel::oneshot, future, FutureExt}; let pending = Arc::new(AtomicUsize::new(0)); let counter = pending.clone(); let mut io = core::IoHandler::default(); - io.add_method("hello", |_params: core::Params| Ok(core::Value::String("world".into()))); + io.add_sync_method("hello", |_params: core::Params| Ok(core::Value::String("world".into()))); io.add_method("hello_async", |_params: core::Params| { - core::futures::finished(core::Value::String("world".into())) + future::ready(Ok(core::Value::String("world".into()))) }); io.add_method("record_pending", move |_params: core::Params| { counter.fetch_add(1, Ordering::SeqCst); let (send, recv) = oneshot::channel(); - ::std::thread::spawn(move || { - ::std::thread::sleep(Duration::from_millis(500)); + std::thread::spawn(move || { + std::thread::sleep(Duration::from_millis(500)); let _ = send.send(()); }); let counter = counter.clone(); - recv.then(move |res| { + recv.map(move |res| { if res.is_ok() { counter.fetch_sub(1, Ordering::SeqCst); }