From 1f2bd335501607ff8ed33ed51a285d75768d0b2a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 5 Dec 2022 15:22:17 +0100 Subject: [PATCH 01/60] bounded channels --- core/src/server/helpers.rs | 34 ++++++++++--------------------- core/src/server/rpc_module.rs | 22 ++++++++++---------- server/src/server.rs | 17 ++++++++++++++++ server/src/transport/ws.rs | 38 ++++++++++++++++++++++++++--------- 4 files changed, 68 insertions(+), 43 deletions(-) diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index 5b4302a8be..9c40ece4c1 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -29,7 +29,7 @@ use std::sync::Arc; use crate::tracing::tx_log_from_str; use crate::Error; -use futures_channel::mpsc; +use futures_channel::mpsc::{self, TrySendError}; use jsonrpsee_types::error::{ErrorCode, ErrorObject, ErrorResponse, OVERSIZED_RESPONSE_CODE, OVERSIZED_RESPONSE_MSG}; use jsonrpsee_types::{Id, InvalidRequest, Response}; use serde::Serialize; @@ -84,7 +84,7 @@ impl<'a> io::Write for &'a mut BoundedWriter { #[derive(Clone, Debug)] pub struct MethodSink { /// Channel sender. - tx: mpsc::UnboundedSender, + tx: mpsc::Sender, /// Max response size in bytes for a executed call. max_response_size: u32, /// Max log length. @@ -93,12 +93,12 @@ pub struct MethodSink { impl MethodSink { /// Create a new `MethodSink` with unlimited response size. - pub fn new(tx: mpsc::UnboundedSender) -> Self { + pub fn new(tx: mpsc::Sender) -> Self { MethodSink { tx, max_response_size: u32::MAX, max_log_length: u32::MAX } } /// Create a new `MethodSink` with a limited response size. - pub fn new_with_limit(tx: mpsc::UnboundedSender, max_response_size: u32, max_log_length: u32) -> Self { + pub fn new_with_limit(tx: mpsc::Sender, max_response_size: u32, max_log_length: u32) -> Self { MethodSink { tx, max_response_size, max_log_length } } @@ -108,40 +108,28 @@ impl MethodSink { } /// Send a JSON-RPC error to the client - pub fn send_error(&self, id: Id, error: ErrorObject) -> bool { - let json = match serde_json::to_string(&ErrorResponse::borrowed(error, id)) { - Ok(json) => json, - Err(err) => { - tracing::error!("Error serializing response: {:?}", err); - - return false; - } - }; + pub fn send_error(&mut self, id: Id, error: ErrorObject) -> Result<(), TrySendError> { + let json = serde_json::to_string(&ErrorResponse::borrowed(error, id)).expect("valid JSON; qed"); tx_log_from_str(&json, self.max_log_length); - if let Err(err) = self.send_raw(json) { - tracing::warn!("Error sending response {:?}", err); - false - } else { - true - } + self.send_raw(json) } /// Helper for sending the general purpose `Error` as a JSON-RPC errors to the client. - pub fn send_call_error(&self, id: Id, err: Error) -> bool { + pub fn send_call_error(&mut self, id: Id, err: Error) -> Result<(), TrySendError> { self.send_error(id, err.into()) } /// Send a raw JSON-RPC message to the client, `MethodSink` does not check verify the validity /// of the JSON being sent. - pub fn send_raw(&self, json: String) -> Result<(), mpsc::TrySendError> { + pub fn send_raw(&mut self, json: String) -> Result<(), TrySendError> { tx_log_from_str(&json, self.max_log_length); - self.tx.unbounded_send(json) + self.tx.try_send(json) } /// Close the channel for any further messages. - pub fn close(&self) { + pub fn close(&mut self) { self.tx.close_channel(); } diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 273856603d..58d18b06d7 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -83,9 +83,9 @@ pub type MaxResponseSize = usize; /// Raw response from an RPC /// A 3-tuple containing: /// - Call result as a `String`, -/// - a [`mpsc::UnboundedReceiver`] to receive future subscription results +/// - a [`mpsc::Receiver`] to receive future subscription results /// - a [`crate::server::helpers::SubscriptionPermit`] to allow subscribers to notify their [`SubscriptionSink`] when they disconnect. -pub type RawRpcResponse = (MethodResponse, mpsc::UnboundedReceiver, SubscriptionPermit); +pub type RawRpcResponse = (MethodResponse, mpsc::Receiver, SubscriptionPermit); /// Helper struct to manage subscriptions. pub struct ConnState<'a> { @@ -404,10 +404,7 @@ impl Methods { /// ); /// } /// ``` - pub async fn raw_json_request( - &self, - request: &str, - ) -> Result<(MethodResponse, mpsc::UnboundedReceiver), Error> { + pub async fn raw_json_request(&self, request: &str) -> Result<(MethodResponse, mpsc::Receiver), Error> { tracing::trace!("[Methods::raw_json_request] Request: {:?}", request); let req: Request = serde_json::from_str(request)?; let (resp, rx, _) = self.inner_call(req).await; @@ -416,7 +413,7 @@ impl Methods { /// Execute a callback. async fn inner_call(&self, req: Request<'_>) -> RawRpcResponse { - let (tx_sink, mut rx_sink) = mpsc::unbounded(); + let (tx_sink, mut rx_sink) = mpsc::channel(u32::MAX as usize / 2); let sink = MethodSink::new(tx_sink); let id = req.id.clone(); let params = Params::new(req.params.map(|params| params.get())); @@ -750,7 +747,6 @@ impl RpcModule { ); } - // TODO: register as failed in !result. MethodResponse::response(id, result, max_response_size) })), ); @@ -1065,7 +1061,11 @@ impl SubscriptionSink { } } - fn answer_subscription(&self, response: MethodResponse, subscribe_call: oneshot::Sender) -> bool { + fn answer_subscription( + &mut self, + response: MethodResponse, + subscribe_call: oneshot::Sender, + ) -> bool { let ws_send = self.inner.send_raw(response.result.clone()).is_ok(); let logger_call = subscribe_call.send(response).is_ok(); @@ -1110,7 +1110,7 @@ impl SubscriptionSink { /// pub fn close(self, err: impl Into) -> bool { if self.is_active_subscription() { - if let Some((sink, _)) = self.subscribers.lock().remove(&self.uniq_sub) { + if let Some((mut sink, _)) = self.subscribers.lock().remove(&self.uniq_sub) { tracing::debug!("Closing subscription: {:?}", self.uniq_sub.sub_id); let msg = self.build_error_message(&err.into()).expect("valid json infallible; qed"); @@ -1140,7 +1140,7 @@ impl Drop for SubscriptionSink { #[derive(Debug)] pub struct Subscription { close_notify: Option, - rx: mpsc::UnboundedReceiver, + rx: mpsc::Receiver, sub_id: RpcSubscriptionId<'static>, } diff --git a/server/src/server.rs b/server/src/server.rs index 91d26dba07..05f6419909 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -157,6 +157,7 @@ where max_connections: self.cfg.max_connections, enable_http: self.cfg.enable_http, enable_ws: self.cfg.enable_ws, + buffer_capacity: self.cfg.buffer_capacity, }; process_connection(&self.service_builder, &connection_guard, data, socket, &mut connections); id = id.wrapping_add(1); @@ -199,6 +200,8 @@ struct Settings { enable_http: bool, /// Enable WS. enable_ws: bool, + /// Number of messages that server is allowed `buffer` until backpressure kicks in. + buffer_capacity: u32, } impl Default for Settings { @@ -215,6 +218,7 @@ impl Default for Settings { ping_interval: Duration::from_secs(60), enable_http: true, enable_ws: true, + buffer_capacity: 1024, } } } @@ -453,6 +457,14 @@ impl Builder { self } + /// Configure the max number of messages that can be buffered + /// + /// If this limit is exceeded the message can be dropped or the connection could be closed. + pub fn set_buffer_size(mut self, c: u32) -> Self { + self.settings.buffer_capacity = c; + self + } + /// Finalize the configuration of the server. Consumes the [`Builder`]. /// /// ```rust @@ -579,6 +591,8 @@ pub(crate) struct ServiceData { pub(crate) enable_http: bool, /// Enable WS. pub(crate) enable_ws: bool, + /// Bounded channel capacity. + pub(crate) buffer_capacity: u32, } /// JsonRPSee service compatible with `tower`. @@ -770,6 +784,8 @@ struct ProcessConnection { enable_http: bool, /// Allow JSON-RPC WS request and WS upgrade requests. enable_ws: bool, + /// ... + buffer_capacity: u32, } #[instrument(name = "connection", skip_all, fields(remote_addr = %cfg.remote_addr, conn_id = %cfg.conn_id), level = "INFO")] @@ -829,6 +845,7 @@ fn process_connection<'a, L: Logger, B, U>( conn: Arc::new(conn), enable_http: cfg.enable_http, enable_ws: cfg.enable_ws, + buffer_capacity: cfg.buffer_capacity, }, }; diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index f2ed5b375d..9178e5ebd0 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -289,13 +289,14 @@ pub(crate) async fn background_task( conn_id, logger, remote_addr, + buffer_capacity, conn, .. } = svc; - let (tx, rx) = mpsc::unbounded::(); + let (tx, rx) = mpsc::channel::(buffer_capacity as usize); let bounded_subscriptions = BoundedSubscriptions::new(max_subscriptions_per_connection); - let sink = MethodSink::new_with_limit(tx, max_response_body_size, max_log_length); + let mut sink = MethodSink::new_with_limit(tx, max_response_body_size, max_log_length); // Spawn another task that sends out the responses on the Websocket. tokio::spawn(send_task(rx, sender, stop_handle.clone(), ping_interval)); @@ -339,7 +340,13 @@ pub(crate) async fn background_task( current, maximum ); - sink.send_error(Id::Null, reject_too_big_request(max_request_body_size)); + if let Err(e) = sink.send_error(Id::Null, reject_too_big_request(max_request_body_size)) { + if e.is_full() { + return Err(Error::Custom("Server buffer capacity exceeded".to_string())); + } else { + return Ok(()); + } + } continue; } @@ -361,7 +368,7 @@ pub(crate) async fn background_task( match first_non_whitespace { Some(b'{') => { let data = std::mem::take(&mut data); - let sink = sink.clone(); + let mut sink = sink.clone(); let resources = &resources; let methods = &methods; let bounded_subscriptions = bounded_subscriptions.clone(); @@ -387,7 +394,8 @@ pub(crate) async fn background_task( } MethodResult::SendAndLogger(r) => { logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); - let _ = sink.send_raw(r.result); + // TODO: close conn?!. + if let Err(e) = sink.send_raw(r.result) {} } }; } @@ -401,14 +409,20 @@ pub(crate) async fn background_task( ErrorObject::borrowed(BATCHES_NOT_SUPPORTED_CODE, &BATCHES_NOT_SUPPORTED_MSG, None), ); logger.on_response(&response.result, request_start, TransportProtocol::WebSocket); - let _ = sink.send_raw(response.result); + if let Err(e) = sink.send_raw(response.result) { + if e.is_full() { + return Err(Error::Custom("Connection buffer limit exceeded".to_string())); + } else { + return Ok(()); + } + } } Some(b'[') => { // Make sure the following variables are not moved into async closure below. let resources = &resources; let methods = &methods; let bounded_subscriptions = bounded_subscriptions.clone(); - let sink = sink.clone(); + let mut sink = sink.clone(); let id_provider = id_provider.clone(); let data = std::mem::take(&mut data); @@ -440,7 +454,13 @@ pub(crate) async fn background_task( method_executors.add(Box::pin(fut)); } _ => { - sink.send_error(Id::Null, ErrorCode::ParseError.into()); + if let Err(e) = sink.send_error(Id::Null, ErrorCode::ParseError.into()) { + if e.is_full() { + return Err(Error::Custom("Server buffer capacity exceeded".to_string())); + } else { + return Ok(()); + } + } } } }; @@ -463,7 +483,7 @@ pub(crate) async fn background_task( /// A task that waits for new messages via the `rx channel` and sends them out on the `WebSocket`. async fn send_task( - mut rx: mpsc::UnboundedReceiver, + mut rx: mpsc::Receiver, mut ws_sender: Sender, mut stop_handle: StopHandle, ping_interval: Duration, From cf1b122f1db5f74bdba132ac557144779d77344e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 7 Dec 2022 13:21:40 +0100 Subject: [PATCH 02/60] remove bounded subscriptions --- core/src/error.rs | 7 ++ core/src/server/helpers.rs | 82 ++------------- core/src/server/rpc_module.rs | 127 ++++++++++++++++------- examples/examples/ws_pubsub_broadcast.rs | 1 + server/src/server.rs | 9 +- server/src/transport/ws.rs | 65 +++++------- tests/tests/integration_tests.rs | 9 +- tests/tests/rpc_module.rs | 10 +- types/Cargo.toml | 2 +- types/src/error.rs | 2 + 10 files changed, 148 insertions(+), 166 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index 9b2b181996..2614aef564 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -172,6 +172,8 @@ pub enum SubscriptionClosed { RemotePeerAborted, /// The subscription was completed successfully by the server. Success, + /// The subscription was dropped because the message capacity buffer was exceeded. + Full, /// The subscription failed during execution by the server. Failed(ErrorObject<'static>), } @@ -182,6 +184,11 @@ impl From for ErrorObjectOwned { SubscriptionClosed::RemotePeerAborted => { ErrorObject::owned(SUBSCRIPTION_CLOSED, "Subscription was closed by the remote peer", None::<()>) } + SubscriptionClosed::Full => ErrorObject::owned( + SUBSCRIPTION_CLOSED, + "Subscription was closed because the bounded buffer capacity was exceeded", + None::<()>, + ), SubscriptionClosed::Success => ErrorObject::owned( SUBSCRIPTION_CLOSED, "Subscription was completed by the server successfully", diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index 9c40ece4c1..24bb18c701 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -25,15 +25,15 @@ // DEALINGS IN THE SOFTWARE. use std::io; -use std::sync::Arc; use crate::tracing::tx_log_from_str; use crate::Error; -use futures_channel::mpsc::{self, TrySendError}; +use futures_channel::mpsc; use jsonrpsee_types::error::{ErrorCode, ErrorObject, ErrorResponse, OVERSIZED_RESPONSE_CODE, OVERSIZED_RESPONSE_MSG}; use jsonrpsee_types::{Id, InvalidRequest, Response}; use serde::Serialize; -use tokio::sync::{Notify, OwnedSemaphorePermit, Semaphore}; + +use super::rpc_module::SendError; /// Bounded writer that allows writing at most `max_len` bytes. /// @@ -108,7 +108,7 @@ impl MethodSink { } /// Send a JSON-RPC error to the client - pub fn send_error(&mut self, id: Id, error: ErrorObject) -> Result<(), TrySendError> { + pub fn send_error(&mut self, id: Id, error: ErrorObject) -> Result<(), SendError> { let json = serde_json::to_string(&ErrorResponse::borrowed(error, id)).expect("valid JSON; qed"); tx_log_from_str(&json, self.max_log_length); @@ -117,15 +117,15 @@ impl MethodSink { } /// Helper for sending the general purpose `Error` as a JSON-RPC errors to the client. - pub fn send_call_error(&mut self, id: Id, err: Error) -> Result<(), TrySendError> { + pub fn send_call_error(&mut self, id: Id, err: Error) -> Result<(), SendError> { self.send_error(id, err.into()) } /// Send a raw JSON-RPC message to the client, `MethodSink` does not check verify the validity /// of the JSON being sent. - pub fn send_raw(&mut self, json: String) -> Result<(), TrySendError> { + pub fn send_raw(&mut self, json: String) -> Result<(), SendError> { tx_log_from_str(&json, self.max_log_length); - self.tx.try_send(json) + self.tx.try_send(json).map_err(Into::into) } /// Close the channel for any further messages. @@ -148,59 +148,6 @@ pub fn prepare_error(data: &[u8]) -> (Id<'_>, ErrorCode) { } } -/// A permitted subscription. -#[derive(Debug)] -pub struct SubscriptionPermit { - _permit: OwnedSemaphorePermit, - resource: Arc, -} - -impl SubscriptionPermit { - /// Get the handle to [`tokio::sync::Notify`]. - pub fn handle(&self) -> Arc { - self.resource.clone() - } -} - -/// Wrapper over [`tokio::sync::Notify`] with bounds check. -#[derive(Debug, Clone)] -pub struct BoundedSubscriptions { - resource: Arc, - guard: Arc, - max: u32, -} - -impl BoundedSubscriptions { - /// Create a new bounded subscription. - pub fn new(max_subscriptions: u32) -> Self { - Self { - resource: Arc::new(Notify::new()), - guard: Arc::new(Semaphore::new(max_subscriptions as usize)), - max: max_subscriptions, - } - } - - /// Attempts to acquire a subscription slot. - /// - /// Fails if `max_subscriptions` have been exceeded. - pub fn acquire(&self) -> Option { - Arc::clone(&self.guard) - .try_acquire_owned() - .ok() - .map(|p| SubscriptionPermit { _permit: p, resource: self.resource.clone() }) - } - - /// Get the maximum number of permitted subscriptions. - pub const fn max(&self) -> u32 { - self.max - } - - /// Close all subscriptions. - pub fn close(&self) { - self.resource.notify_waiters(); - } -} - /// Represent the response to method call. #[derive(Debug, Clone)] pub struct MethodResponse { @@ -319,7 +266,6 @@ impl BatchResponse { #[cfg(test)] mod tests { - use crate::server::helpers::BoundedSubscriptions; use super::{BatchResponseBuilder, BoundedWriter, Id, MethodResponse, Response}; @@ -339,20 +285,6 @@ mod tests { assert!(serde_json::to_writer(&mut writer, &"x".repeat(99)).is_err()); } - #[test] - fn bounded_subscriptions_work() { - let subs = BoundedSubscriptions::new(5); - let mut handles = Vec::new(); - - for _ in 0..5 { - handles.push(subs.acquire().unwrap()); - } - - assert!(subs.acquire().is_none()); - handles.swap_remove(0); - assert!(subs.acquire().is_some()); - } - #[test] fn batch_with_single_works() { let method = MethodResponse::response(Id::Number(1), "a", usize::MAX); diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 58d18b06d7..3168a5b8bf 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -32,9 +32,10 @@ use std::sync::Arc; use crate::error::{Error, SubscriptionClosed}; use crate::id_providers::RandomIntegerIdProvider; -use crate::server::helpers::{BoundedSubscriptions, MethodSink, SubscriptionPermit}; +use crate::server::helpers::MethodSink; use crate::server::resource_limiting::{ResourceGuard, ResourceTable, ResourceVec, Resources}; use crate::traits::{IdProvider, ToRpcParams}; +use futures_channel::mpsc::TrySendError; use futures_channel::{mpsc, oneshot}; use futures_util::future::Either; use futures_util::pin_mut; @@ -51,7 +52,7 @@ use jsonrpsee_types::{ use parking_lot::Mutex; use rustc_hash::FxHashMap; use serde::{de::DeserializeOwned, Serialize}; -use tokio::sync::watch; +use tokio::sync::{watch, Notify}; use super::helpers::MethodResponse; @@ -85,14 +86,14 @@ pub type MaxResponseSize = usize; /// - Call result as a `String`, /// - a [`mpsc::Receiver`] to receive future subscription results /// - a [`crate::server::helpers::SubscriptionPermit`] to allow subscribers to notify their [`SubscriptionSink`] when they disconnect. -pub type RawRpcResponse = (MethodResponse, mpsc::Receiver, SubscriptionPermit); +pub type RawRpcResponse = (MethodResponse, mpsc::Receiver, Arc); /// Helper struct to manage subscriptions. pub struct ConnState<'a> { /// Connection ID pub conn_id: ConnectionId, /// Get notified when the connection to subscribers is closed. - pub close_notify: SubscriptionPermit, + pub close_notify: Arc, /// ID provider. pub id_provider: &'a dyn IdProvider, } @@ -106,6 +107,21 @@ pub enum InnerSubscriptionResult { Aborted, } +/// Sending stuff via a subscription can either fail if the subscription failed or that +/// actual connection is full or disconnected. +#[derive(Debug, thiserror::Error)] +pub enum SubscriptionSinkError { + /// Something failed during the init of the subscription. + #[error("{0:?}")] + Subscribe(SubscriptionAcceptRejectError), + #[error("{0}")] + /// Something failed when sending a message via the subscription. + Send(#[from] SendError), + #[error("{0}")] + /// Something when trying to decode the message. + Serialize(#[from] serde_json::Error), +} + impl<'a> std::fmt::Debug for ConnState<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ConnState").field("conn_id", &self.conn_id).field("close", &self.close_notify).finish() @@ -417,16 +433,15 @@ impl Methods { let sink = MethodSink::new(tx_sink); let id = req.id.clone(); let params = Params::new(req.params.map(|params| params.get())); - let bounded_subs = BoundedSubscriptions::new(u32::MAX); - let close_notify = bounded_subs.acquire().expect("u32::MAX permits is sufficient; qed"); - let notify = bounded_subs.acquire().expect("u32::MAX permits is sufficient; qed"); + let close_notify = Arc::new(Notify::new()); let response = match self.method(&req.method).map(|c| &c.callback) { None => MethodResponse::error(req.id, ErrorObject::from(ErrorCode::MethodNotFound)), Some(MethodKind::Sync(cb)) => (cb)(id, params, usize::MAX), Some(MethodKind::Async(cb)) => (cb)(id.into_owned(), params.into_owned(), 0, usize::MAX, None).await, Some(MethodKind::Subscription(cb)) => { - let conn_state = ConnState { conn_id: 0, close_notify, id_provider: &RandomIntegerIdProvider }; + let conn_state = + ConnState { conn_id: 0, close_notify: close_notify.clone(), id_provider: &RandomIntegerIdProvider }; let res = (cb)(id, params, sink.clone(), conn_state, None).await; // This message is not used because it's used for metrics so we discard in other to @@ -442,7 +457,7 @@ impl Methods { tracing::trace!("[Methods::inner_call] Method: {}, response: {:?}", req.method, response); - (response, rx_sink, notify) + (response, rx_sink, close_notify) } /// Helper to create a subscription on the `RPC module` without having to spin up a server. @@ -764,7 +779,7 @@ impl RpcModule { let sink = SubscriptionSink { inner: method_sink, - close_notify: Some(conn.close_notify), + close_notify: conn.close_notify, method: notif_method_name, subscribers: subscribers.clone(), uniq_sub, @@ -813,13 +828,43 @@ impl RpcModule { /// Returns once the unsubscribe method has been called. type UnsubscribeCall = Option>; +/// Represents a send error over a bounded channel. +#[derive(Debug, Copy, Clone, thiserror::Error)] +pub enum SendError { + /// The channel was full. + #[error("The channel is full")] + Full, + /// The channel was disconnected. + #[error("The channel is disconnected")] + Disconnected, +} + +impl From> for SendError { + fn from(err: TrySendError) -> Self { + if err.is_full() { + Self::Full + } else { + Self::Disconnected + } + } +} + +impl From for SubscriptionAcceptRejectError { + fn from(err: SendError) -> Self { + match err { + SendError::Disconnected => Self::RemotePeerAborted, + SendError::Full => Self::Full, + } + } +} + /// Represents a single subscription. #[derive(Debug)] pub struct SubscriptionSink { /// Sink. inner: MethodSink, /// Get notified when subscribers leave so we can exit - close_notify: Option, + close_notify: Arc, /// MethodCallback. method: &'static str, /// Shared Mutex of subscriptions for this method. @@ -844,11 +889,8 @@ impl SubscriptionSink { let err = MethodResponse::error(id, err.into()); - if self.answer_subscription(err, subscribe_call) { - Ok(()) - } else { - Err(SubscriptionAcceptRejectError::RemotePeerAborted) - } + self.answer_subscription(err, subscribe_call)?; + Ok(()) } /// Attempt to accept the subscription and respond the subscription method call. @@ -860,14 +902,15 @@ impl SubscriptionSink { let response = MethodResponse::response(id, &self.uniq_sub.sub_id, self.inner.max_response_size() as usize); let success = response.success; - let sent = self.answer_subscription(response, subscribe_call); + self.answer_subscription(response, subscribe_call)?; - if sent && success { + if success { let (tx, rx) = watch::channel(()); self.subscribers.lock().insert(self.uniq_sub.clone(), (self.inner.clone(), tx)); self.unsubscribe = Some(rx); Ok(()) } else { + // TODO(niklasad1): this is wrong, the response was too big to be sent. Err(SubscriptionAcceptRejectError::RemotePeerAborted) } } @@ -891,11 +934,15 @@ impl SubscriptionSink { /// - `Ok(false)` if the sink was closed (either because the subscription was closed or the connection was terminated), /// or the subscription could not be accepted. /// - `Err(err)` if the message could not be serialized. - pub fn send(&mut self, result: &T) -> Result { - // Cannot accept the subscription. - if let Err(SubscriptionAcceptRejectError::RemotePeerAborted) = self.accept() { - return Ok(false); - } + pub fn send(&mut self, result: &T) -> Result<(), SubscriptionSinkError> { + match self.accept() { + Ok(_) => (), + Err(SubscriptionAcceptRejectError::AlreadyCalled) => (), + Err(SubscriptionAcceptRejectError::Full) => return Err(SubscriptionSinkError::Send(SendError::Full)), + Err(SubscriptionAcceptRejectError::RemotePeerAborted) => { + return Err(SubscriptionSinkError::Send(SendError::Disconnected)) + } + }; self.send_without_accept(result) } @@ -952,10 +999,7 @@ impl SubscriptionSink { return SubscriptionClosed::RemotePeerAborted; } - let conn_closed = match self.close_notify.as_ref().map(|cn| cn.handle()) { - Some(cn) => cn, - None => return SubscriptionClosed::RemotePeerAborted, - }; + let conn_closed = self.close_notify.clone(); let mut sub_closed = match self.unsubscribe.as_ref() { Some(rx) => rx.clone(), @@ -982,10 +1026,13 @@ impl SubscriptionSink { // The app sent us a value to send back to the subscribers Either::Left((Ok(Some(result)), next_closed_fut)) => { match self.send_without_accept(&result) { - Ok(true) => (), - Ok(false) => { + Ok(()) => (), + Err(SubscriptionSinkError::Send(SendError::Disconnected)) => { break SubscriptionClosed::RemotePeerAborted; } + Err(SubscriptionSinkError::Send(SendError::Full)) => { + break SubscriptionClosed::Full; + } Err(err) => { let err = ErrorObject::owned(SUBSCRIPTION_CLOSED_WITH_ERROR, err.to_string(), None::<()>); break SubscriptionClosed::Failed(err); @@ -1036,7 +1083,7 @@ impl SubscriptionSink { /// Returns whether the subscription is closed. pub fn is_closed(&self) -> bool { - self.inner.is_closed() || self.close_notify.is_none() || !self.is_active_subscription() + self.inner.is_closed() || !self.is_active_subscription() } /// Send a message back to subscribers. @@ -1044,14 +1091,14 @@ impl SubscriptionSink { /// This is similar to the [`SubscriptionSink::send`], but it does not try to accept /// the subscription prior to sending. #[inline] - fn send_without_accept(&mut self, result: &T) -> Result { + fn send_without_accept(&mut self, result: &T) -> Result<(), SubscriptionSinkError> { // Only possible to trigger when the connection is dropped. if self.is_closed() { - return Ok(false); + return Err(SubscriptionSinkError::Send(SendError::Disconnected)); } let msg = self.build_message(result)?; - Ok(self.inner.send_raw(msg).is_ok()) + self.inner.send_raw(msg).map_err(|e| SubscriptionSinkError::Send(e.into())) } fn is_active_subscription(&self) -> bool { @@ -1065,11 +1112,11 @@ impl SubscriptionSink { &mut self, response: MethodResponse, subscribe_call: oneshot::Sender, - ) -> bool { - let ws_send = self.inner.send_raw(response.result.clone()).is_ok(); - let logger_call = subscribe_call.send(response).is_ok(); + ) -> Result<(), SendError> { + self.inner.send_raw(response.result.clone())?; + subscribe_call.send(response).map_err(|_| SendError::Disconnected)?; - ws_send && logger_call + Ok(()) } fn build_message(&self, result: &T) -> Result { @@ -1129,7 +1176,7 @@ impl Drop for SubscriptionSink { // because that's how the previous PendingSubscription logic // worked. let err = MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidParams)); - self.answer_subscription(err, subscribe_call); + let _ = self.answer_subscription(err, subscribe_call); } else if self.is_active_subscription() { self.subscribers.lock().remove(&self.uniq_sub); } @@ -1139,7 +1186,7 @@ impl Drop for SubscriptionSink { /// Wrapper struct that maintains a subscription "mainly" for testing. #[derive(Debug)] pub struct Subscription { - close_notify: Option, + close_notify: Option>, rx: mpsc::Receiver, sub_id: RpcSubscriptionId<'static>, } @@ -1149,7 +1196,7 @@ impl Subscription { pub fn close(&mut self) { tracing::trace!("[Subscription::close] Notifying"); if let Some(n) = self.close_notify.take() { - n.handle().notify_one() + n.notify_one() } } diff --git a/examples/examples/ws_pubsub_broadcast.rs b/examples/examples/ws_pubsub_broadcast.rs index 9aaca568c3..116d445e00 100644 --- a/examples/examples/ws_pubsub_broadcast.rs +++ b/examples/examples/ws_pubsub_broadcast.rs @@ -80,6 +80,7 @@ async fn run_server() -> anyhow::Result { sink.close(SubscriptionClosed::Success); } SubscriptionClosed::RemotePeerAborted => (), + SubscriptionClosed::Full => (), SubscriptionClosed::Failed(err) => { sink.close(err); } diff --git a/server/src/server.rs b/server/src/server.rs index 05f6419909..c254d6c5e4 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -129,7 +129,6 @@ where let logger = self.logger; let batch_requests_supported = self.cfg.batch_requests_supported; let id_provider = self.id_provider; - let max_subscriptions_per_connection = self.cfg.max_subscriptions_per_connection; let mut id: u32 = 0; let connection_guard = ConnectionGuard::new(self.cfg.max_connections as usize); @@ -151,7 +150,6 @@ where id_provider: id_provider.clone(), ping_interval: self.cfg.ping_interval, stop_handle: stop_handle.clone(), - max_subscriptions_per_connection, conn_id: id, logger: logger.clone(), max_connections: self.cfg.max_connections, @@ -287,7 +285,7 @@ impl Builder { /// Register a new resource kind. Errors if `label` is already registered, or if the number of /// registered resources on this server instance would exceed 8. /// - /// See the module documentation for [`resurce_limiting`](../jsonrpsee_utils/server/resource_limiting/index.html#resource-limiting) + /// See the module documentation for [`resource_limiting`](../jsonrpsee_utils/server/resource_limiting/index.html#resource-limiting) /// for details. pub fn register_resource(mut self, label: &'static str, capacity: u16, default: u16) -> Result { self.resources.register(label, capacity, default)?; @@ -579,8 +577,6 @@ pub(crate) struct ServiceData { pub(crate) ping_interval: Duration, /// Stop handle. pub(crate) stop_handle: StopHandle, - /// Max subscriptions per connection. - pub(crate) max_subscriptions_per_connection: u32, /// Connection ID pub(crate) conn_id: u32, /// Logger. @@ -772,8 +768,6 @@ struct ProcessConnection { ping_interval: Duration, /// Stop handle. stop_handle: StopHandle, - /// Max subscriptions per connection. - max_subscriptions_per_connection: u32, /// Max connections, max_connections: u32, /// Connection ID @@ -839,7 +833,6 @@ fn process_connection<'a, L: Logger, B, U>( id_provider: cfg.id_provider, ping_interval: cfg.ping_interval, stop_handle: cfg.stop_handle.clone(), - max_subscriptions_per_connection: cfg.max_subscriptions_per_connection, conn_id: cfg.conn_id, logger: cfg.logger, conn: Arc::new(conn), diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 9178e5ebd0..0a1209e342 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -1,4 +1,5 @@ use std::pin::Pin; +use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; @@ -12,21 +13,19 @@ use futures_util::io::{BufReader, BufWriter}; use futures_util::stream::FuturesOrdered; use futures_util::{Future, FutureExt, StreamExt}; use hyper::upgrade::Upgraded; -use jsonrpsee_core::server::helpers::{ - prepare_error, BatchResponse, BatchResponseBuilder, BoundedSubscriptions, MethodResponse, MethodSink, -}; +use jsonrpsee_core::server::helpers::{prepare_error, BatchResponse, BatchResponseBuilder, MethodResponse, MethodSink}; use jsonrpsee_core::server::resource_limiting::Resources; -use jsonrpsee_core::server::rpc_module::{ConnState, MethodKind, Methods}; +use jsonrpsee_core::server::rpc_module::{ConnState, MethodKind, Methods, SendError}; use jsonrpsee_core::tracing::{rx_log_from_json, tx_log_from_str}; use jsonrpsee_core::traits::IdProvider; use jsonrpsee_core::{Error, JsonRawValue}; use jsonrpsee_types::error::{ - reject_too_big_request, reject_too_many_subscriptions, ErrorCode, BATCHES_NOT_SUPPORTED_CODE, - BATCHES_NOT_SUPPORTED_MSG, + reject_too_big_request, ErrorCode, BATCHES_NOT_SUPPORTED_CODE, BATCHES_NOT_SUPPORTED_MSG, }; use jsonrpsee_types::{ErrorObject, Id, InvalidRequest, Notification, Params, Request}; use soketto::connection::Error as SokettoError; use soketto::data::ByteSlice125; +use tokio::sync::Notify; use tokio_stream::wrappers::IntervalStream; use tokio_util::compat::Compat; use tracing::instrument; @@ -58,7 +57,6 @@ pub(crate) struct Batch<'a, L: Logger> { #[derive(Debug, Clone)] pub(crate) struct CallData<'a, L: Logger> { pub(crate) conn_id: usize, - pub(crate) bounded_subscriptions: BoundedSubscriptions, pub(crate) id_provider: &'a dyn IdProvider, pub(crate) methods: &'a Methods, pub(crate) max_response_body_size: u32, @@ -67,6 +65,7 @@ pub(crate) struct CallData<'a, L: Logger> { pub(crate) sink: &'a MethodSink, pub(crate) logger: &'a L, pub(crate) request_start: L::Instant, + pub(crate) close_notify: Arc, } /// This is a glorified select listening for new messages, while also checking the `stop_receiver` signal. @@ -179,11 +178,11 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData max_response_body_size, max_log_length, conn_id, - bounded_subscriptions, id_provider, sink, logger, request_start, + close_notify, } = call; rx_log_from_json(&req, call.max_log_length); @@ -236,15 +235,9 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData logger.on_call(name, params.clone(), logger::MethodKind::Subscription, TransportProtocol::WebSocket); match method.claim(name, resources) { Ok(guard) => { - if let Some(cn) = bounded_subscriptions.acquire() { - let conn_state = ConnState { conn_id, close_notify: cn, id_provider }; - let response = callback(id.clone(), params, sink.clone(), conn_state, Some(guard)).await; - MethodResult::JustLogger(response) - } else { - let response = - MethodResponse::error(id, reject_too_many_subscriptions(bounded_subscriptions.max())); - MethodResult::SendAndLogger(response) - } + let conn_state = ConnState { conn_id, close_notify, id_provider }; + let response = callback(id.clone(), params, sink.clone(), conn_state, Some(guard)).await; + MethodResult::JustLogger(response) } Err(err) => { tracing::error!("[Methods::execute_with_resources] failed to lock resources: {}", err); @@ -285,7 +278,6 @@ pub(crate) async fn background_task( stop_handle, id_provider, ping_interval, - max_subscriptions_per_connection, conn_id, logger, remote_addr, @@ -295,8 +287,8 @@ pub(crate) async fn background_task( } = svc; let (tx, rx) = mpsc::channel::(buffer_capacity as usize); - let bounded_subscriptions = BoundedSubscriptions::new(max_subscriptions_per_connection); let mut sink = MethodSink::new_with_limit(tx, max_response_body_size, max_log_length); + let close_notify = Arc::new(Notify::new()); // Spawn another task that sends out the responses on the Websocket. tokio::spawn(send_task(rx, sender, stop_handle.clone(), ping_interval)); @@ -341,10 +333,11 @@ pub(crate) async fn background_task( maximum ); if let Err(e) = sink.send_error(Id::Null, reject_too_big_request(max_request_body_size)) { - if e.is_full() { - return Err(Error::Custom("Server buffer capacity exceeded".to_string())); - } else { - return Ok(()); + match e { + SendError::Full => { + return Err(Error::Custom("Server buffer capacity exceeded".to_string())) + } + SendError::Disconnected => return Ok(()), } } continue; @@ -371,8 +364,8 @@ pub(crate) async fn background_task( let mut sink = sink.clone(); let resources = &resources; let methods = &methods; - let bounded_subscriptions = bounded_subscriptions.clone(); let id_provider = &*id_provider; + let close_notify = close_notify.clone(); let fut = async move { let call = CallData { @@ -381,11 +374,11 @@ pub(crate) async fn background_task( max_response_body_size, max_log_length, methods, - bounded_subscriptions, sink: &sink, id_provider, logger, request_start, + close_notify: close_notify.clone(), }; match process_single_request(data, call).await { @@ -395,7 +388,7 @@ pub(crate) async fn background_task( MethodResult::SendAndLogger(r) => { logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); // TODO: close conn?!. - if let Err(e) = sink.send_raw(r.result) {} + if let Err(_) = sink.send_raw(r.result) {} } }; } @@ -410,10 +403,9 @@ pub(crate) async fn background_task( ); logger.on_response(&response.result, request_start, TransportProtocol::WebSocket); if let Err(e) = sink.send_raw(response.result) { - if e.is_full() { - return Err(Error::Custom("Connection buffer limit exceeded".to_string())); - } else { - return Ok(()); + match e { + SendError::Full => return Err(Error::Custom("Server buffer capacity exceeded".to_string())), + SendError::Disconnected => return Ok(()), } } } @@ -421,10 +413,10 @@ pub(crate) async fn background_task( // Make sure the following variables are not moved into async closure below. let resources = &resources; let methods = &methods; - let bounded_subscriptions = bounded_subscriptions.clone(); let mut sink = sink.clone(); let id_provider = id_provider.clone(); let data = std::mem::take(&mut data); + let close_notify = close_notify.clone(); let fut = async move { let response = process_batch_request(Batch { @@ -435,11 +427,11 @@ pub(crate) async fn background_task( max_response_body_size, max_log_length, methods, - bounded_subscriptions, sink: &sink, id_provider: &*id_provider, logger, request_start, + close_notify, }, }) .await; @@ -455,10 +447,9 @@ pub(crate) async fn background_task( } _ => { if let Err(e) = sink.send_error(Id::Null, ErrorCode::ParseError.into()) { - if e.is_full() { - return Err(Error::Custom("Server buffer capacity exceeded".to_string())); - } else { - return Ok(()); + match e { + SendError::Full => return Err(Error::Custom("Server buffer capacity exceeded".to_string())), + SendError::Disconnected => return Ok(()), } } } @@ -474,7 +465,7 @@ pub(crate) async fn background_task( // Notify all listeners and close down associated tasks. sink.close(); - bounded_subscriptions.close(); + close_notify.notify_waiters(); drop(conn); diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 402e6cd074..54f47327ef 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -41,6 +41,7 @@ use hyper::http::HeaderValue; use jsonrpsee::core::client::{ClientT, IdKind, Subscription, SubscriptionClientT}; use jsonrpsee::core::error::SubscriptionClosed; use jsonrpsee::core::params::{ArrayParams, BatchRequestBuilder}; +use jsonrpsee::core::server::rpc_module::{SendError, SubscriptionSinkError}; use jsonrpsee::core::{Error, JsonValue}; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::rpc_params; @@ -439,8 +440,12 @@ async fn ws_server_should_stop_subscription_after_client_drop() { sink.accept().unwrap(); tokio::spawn(async move { let close_err = loop { - if !sink.send(&1_usize).expect("usize can be serialized; qed") { - break ErrorObject::borrowed(0, &"Subscription terminated successfully", None); + match sink.send(&1_usize) { + Ok(_) => (), + Err(SubscriptionSinkError::Send(SendError::Disconnected)) => { + break ErrorObject::borrowed(0, &"Subscription terminated successfully", None) + } + Err(e) => panic!("Unexpected error: {:?}", e), } tokio::time::sleep(Duration::from_millis(100)).await; }; diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index 9382de1248..a4f18c4274 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -276,9 +276,13 @@ async fn close_test_subscribing_without_server() { tracing::debug!("[test] Sink is open, sleeping"); tokio::time::sleep(std::time::Duration::from_millis(500)).await; } - // Get the close reason. - if !sink.send(&"lo").expect("str serializable; qed") { - sink.close(SubscriptionClosed::RemotePeerAborted); + + match sink.send(&"lo") { + Ok(_) => panic!("The sink should be closed"), + Err(SubscriptionSinkError::Send(SendError::Disconnected)) => { + sink.close(SubscriptionClosed::RemotePeerAborted); + } + Err(other) => panic!("Unexpected error: {:?}", other), } }); Ok(()) diff --git a/types/Cargo.toml b/types/Cargo.toml index ae61b067fe..f1d7106909 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -15,4 +15,4 @@ beef = { version = "0.5.1", features = ["impl_serde"] } tracing = { version = "0.1.34", default-features = false } serde = { version = "1", default-features = false, features = ["derive"] } serde_json = { version = "1", default-features = false, features = ["alloc", "raw_value", "std"] } -thiserror = "1.0" +thiserror = "1.0" \ No newline at end of file diff --git a/types/src/error.rs b/types/src/error.rs index 9644faefb3..ed12a6840c 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -122,6 +122,8 @@ pub enum SubscriptionAcceptRejectError { AlreadyCalled, /// The remote peer closed the connection or called the unsubscribe method. RemotePeerAborted, + /// The server capacity was exceeded. + Full, } /// Owned variant of [`ErrorObject`]. From 5d67a4127096cd7a74e2f14b1686a510beb61cdb Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 7 Dec 2022 14:57:07 +0100 Subject: [PATCH 03/60] remove resource limiting --- core/src/server/mod.rs | 2 - core/src/server/resource_limiting.rs | 175 ---------------- core/src/server/rpc_module.rs | 159 +++----------- server/src/server.rs | 45 +--- server/src/transport/http.rs | 52 +---- server/src/transport/ws.rs | 58 +----- tests/tests/integration_tests.rs | 5 +- tests/tests/metrics.rs | 14 +- tests/tests/resource_limiting.rs | 296 --------------------------- 9 files changed, 57 insertions(+), 749 deletions(-) delete mode 100644 core/src/server/resource_limiting.rs delete mode 100644 tests/tests/resource_limiting.rs diff --git a/core/src/server/mod.rs b/core/src/server/mod.rs index b761a8db23..1e5a748076 100644 --- a/core/src/server/mod.rs +++ b/core/src/server/mod.rs @@ -30,7 +30,5 @@ pub mod helpers; /// Host filtering. pub mod host_filtering; -/// Resource limiting. Create generic "resources" and configure their limits to ensure servers are not overloaded. -pub mod resource_limiting; /// JSON-RPC "modules" group sets of methods that belong together and handles method/subscription registration. pub mod rpc_module; diff --git a/core/src/server/resource_limiting.rs b/core/src/server/resource_limiting.rs deleted file mode 100644 index 685712caab..0000000000 --- a/core/src/server/resource_limiting.rs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any -// person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the -// Software without restriction, including without -// limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software -// is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice -// shall be included in all copies or substantial portions -// of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! # Resource Limiting -//! -//! This module handles limiting the capacity of the server to respond to requests. -//! -//! `jsonrpsee` is agnostic about the types of resources available on the server, and the units used are arbitrary. -//! The units are used to model the availability of a resource, be it something mundane like CPU or Memory, -//! or more exotic things like remote API access to a 3rd party service, or use of some external hardware -//! that's under the control of the server. -//! -//! To get the most out of this feature, we suggest benchmarking individual methods to see how many resources they -//! consume, in particular anything critical that is expected to result in a lot of stress on the server, -//! and then defining your units such that the limits (`capacity`) can be adjusted for different hardware configurations. -//! -//! Up to 8 resources can be defined using the [`ServerBuilder::register_resource`](../../../jsonrpsee_server/struct.ServerBuilder.html#method.register_resource) -//! -//! -//! Each method will claim the specified number of units (or the default) for the duration of its execution. -//! Any method execution that would cause the total sum of claimed resource units to exceed -//! the `capacity` of that resource will be denied execution, immediately returning JSON-RPC error object with code `-32604`. -//! -//! Setting the execution cost to `0` equates to the method effectively not being limited by a given resource. Likewise setting the -//! `capacity` to `0` disables any limiting for a given resource. -//! -//! To specify a different than default number of units a method should use, use the `resources` argument in the -//! `#[method]` attribute: -//! -//! ``` -//! # use jsonrpsee::{core::RpcResult, proc_macros::rpc}; -//! # -//! #[rpc(server)] -//! pub trait Rpc { -//! #[method(name = "my_expensive_method", resources("cpu" = 5, "mem" = 2))] -//! async fn my_expensive_method(&self) -> RpcResult<&'static str> { -//! // Do work -//! Ok("hello") -//! } -//! } -//! ``` -//! -//! Alternatively, you can use the `resource` method when creating a module manually without the help of the macro: -//! -//! ``` -//! # use jsonrpsee::{RpcModule, core::RpcResult}; -//! # -//! # fn main() -> RpcResult<()> { -//! # -//! let mut module = RpcModule::new(()); -//! -//! module -//! .register_async_method("my_expensive_method", |_, _| async move { -//! // Do work -//! Ok("hello") -//! })? -//! .resource("cpu", 5)? -//! .resource("mem", 2)?; -//! # Ok(()) -//! # } -//! ``` -//! -//! Each resource needs to have a unique name, such as `"cpu"` or `"memory"`, which can then be used across all -//! [`RpcModule`s](crate::server::rpc_module::RpcModule). In case a module definition uses a resource label not -//! defined on the server, starting the server with such a module will result in a runtime error containing the -//! information about the offending method. - -use std::sync::Arc; - -use crate::Error; -use arrayvec::ArrayVec; -use parking_lot::Mutex; - -// The number of kinds of resources that can be used for limiting. -const RESOURCE_COUNT: usize = 8; - -/// Fixed size table, mapping a resource to a (unitless) value indicating the amount of the resource that is available to RPC calls. -pub type ResourceTable = [u16; RESOURCE_COUNT]; -/// Variable size table, mapping a resource to a (unitless) value indicating the amount of the resource that is available to RPC calls. -pub type ResourceVec = ArrayVec; - -/// User defined resources available to be used by calls on the JSON-RPC server. -/// Each of the 8 possible resource kinds, for instance "cpu", "io", "nanobots", -/// store a maximum `capacity` and a default. A value of `0` means no limits for the given resource. -#[derive(Debug, Default, Clone)] -pub struct Resources { - /// Resources currently in use by executing calls. 0 for unused resource kinds. - totals: Arc>, - /// Max capacity for all resource kinds - pub capacities: ResourceTable, - /// Default value for all resource kinds; unless a method has a resource limit defined, this is the cost of a call (0 means no default limit) - pub defaults: ResourceTable, - /// Labels for every registered resource - pub labels: ResourceVec<&'static str>, -} - -impl Resources { - /// Register a new resource kind. Errors if `label` is already registered, or if the total number of - /// registered resources would exceed 8. - pub fn register(&mut self, label: &'static str, capacity: u16, default: u16) -> Result<(), Error> { - if self.labels.iter().any(|&l| l == label) { - return Err(Error::ResourceNameAlreadyTaken(label)); - } - - let idx = self.labels.len(); - - self.labels.try_push(label).map_err(|_| Error::MaxResourcesReached)?; - - self.capacities[idx] = capacity; - self.defaults[idx] = default; - - Ok(()) - } - - /// Attempt to claim `units` units for each resource, incrementing current totals. - /// If successful, returns a [`ResourceGuard`] which decrements the totals by the same - /// amounts once dropped. - pub fn claim(&self, units: ResourceTable) -> Result { - let mut totals = self.totals.lock(); - let mut sum = *totals; - - for (idx, sum) in sum.iter_mut().enumerate() { - match sum.checked_add(units[idx]) { - Some(s) if s <= self.capacities[idx] => *sum = s, - _ => { - let label = self.labels.get(idx).copied().unwrap_or(""); - - return Err(Error::ResourceAtCapacity(label)); - } - } - } - - *totals = sum; - - Ok(ResourceGuard { totals: self.totals.clone(), units }) - } -} - -/// RAII style "lock" for claimed resources, will automatically release them once dropped. -#[derive(Debug)] -pub struct ResourceGuard { - totals: Arc>, - units: ResourceTable, -} - -impl Drop for ResourceGuard { - fn drop(&mut self) { - for (sum, claimed) in self.totals.lock().iter_mut().zip(self.units) { - *sum -= claimed; - } - } -} diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 3168a5b8bf..6c47609ff2 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -33,7 +33,6 @@ use std::sync::Arc; use crate::error::{Error, SubscriptionClosed}; use crate::id_providers::RandomIntegerIdProvider; use crate::server::helpers::MethodSink; -use crate::server::resource_limiting::{ResourceGuard, ResourceTable, ResourceVec, Resources}; use crate::traits::{IdProvider, ToRpcParams}; use futures_channel::mpsc::TrySendError; use futures_channel::{mpsc, oneshot}; @@ -62,15 +61,11 @@ use super::helpers::MethodResponse; /// back to `jsonrpsee`, and the connection ID (useful for the websocket transport). pub type SyncMethod = Arc MethodResponse>; /// Similar to [`SyncMethod`], but represents an asynchronous handler and takes an additional argument containing a [`ResourceGuard`] if configured. -pub type AsyncMethod<'a> = Arc< - dyn Send - + Sync - + Fn(Id<'a>, Params<'a>, ConnectionId, MaxResponseSize, Option) -> BoxFuture<'a, MethodResponse>, ->; +pub type AsyncMethod<'a> = + Arc, Params<'a>, ConnectionId, MaxResponseSize) -> BoxFuture<'a, MethodResponse>>; /// Method callback for subscriptions. -pub type SubscriptionMethod<'a> = Arc< - dyn Send + Sync + Fn(Id, Params, MethodSink, ConnState, Option) -> BoxFuture<'a, MethodResponse>, ->; +pub type SubscriptionMethod<'a> = + Arc BoxFuture<'a, MethodResponse>>; // Method callback to unsubscribe. type UnsubscriptionMethod = Arc MethodResponse>; @@ -150,21 +145,10 @@ pub enum MethodKind { Unsubscription(UnsubscriptionMethod), } -/// Information about resources the method uses during its execution. Initialized when the the server starts. -#[derive(Clone, Debug)] -enum MethodResources { - /// Uninitialized resource table, mapping string label to units. - Uninitialized(Box<[(&'static str, u16)]>), - /// Initialized resource table containing units for each `ResourceId`. - Initialized(ResourceTable), -} - /// Method callback wrapper that contains a sync or async closure, -/// plus a table with resources it needs to claim to run #[derive(Clone, Debug)] pub struct MethodCallback { callback: MethodKind, - resources: MethodResources, } /// Result of a method, either direct value or a future of one. @@ -184,57 +168,21 @@ impl Debug for MethodResult { } } -/// Builder for configuring resources used by a method. -#[derive(Debug)] -pub struct MethodResourcesBuilder<'a> { - build: ResourceVec<(&'static str, u16)>, - callback: &'a mut MethodCallback, -} - -impl<'a> MethodResourcesBuilder<'a> { - /// Define how many units of a given named resource the method uses during its execution. - pub fn resource(mut self, label: &'static str, units: u16) -> Result { - self.build.try_push((label, units)).map_err(|_| Error::MaxResourcesReached)?; - Ok(self) - } -} - -impl<'a> Drop for MethodResourcesBuilder<'a> { - fn drop(&mut self) { - self.callback.resources = MethodResources::Uninitialized(self.build[..].into()); - } -} - impl MethodCallback { fn new_sync(callback: SyncMethod) -> Self { - MethodCallback { callback: MethodKind::Sync(callback), resources: MethodResources::Uninitialized([].into()) } + MethodCallback { callback: MethodKind::Sync(callback) } } fn new_async(callback: AsyncMethod<'static>) -> Self { - MethodCallback { callback: MethodKind::Async(callback), resources: MethodResources::Uninitialized([].into()) } + MethodCallback { callback: MethodKind::Async(callback) } } fn new_subscription(callback: SubscriptionMethod<'static>) -> Self { - MethodCallback { - callback: MethodKind::Subscription(callback), - resources: MethodResources::Uninitialized([].into()), - } + MethodCallback { callback: MethodKind::Subscription(callback) } } fn new_unsubscription(callback: UnsubscriptionMethod) -> Self { - MethodCallback { - callback: MethodKind::Unsubscription(callback), - resources: MethodResources::Uninitialized([].into()), - } - } - - /// Attempt to claim resources prior to executing a method. On success returns a guard that releases - /// claimed resources when dropped. - pub fn claim(&self, name: &str, resources: &Resources) -> Result { - match self.resources { - MethodResources::Uninitialized(_) => Err(Error::UninitializedMethod(name.into())), - MethodResources::Initialized(units) => resources.claim(units), - } + MethodCallback { callback: MethodKind::Unsubscription(callback) } } /// Get handle to the callback. @@ -287,36 +235,6 @@ impl Methods { } } - /// Initialize resources for all methods in this collection. This method has no effect if called more than once. - pub fn initialize_resources(mut self, resources: &Resources) -> Result { - let callbacks = self.mut_callbacks(); - - for (&method_name, callback) in callbacks.iter_mut() { - if let MethodResources::Uninitialized(uninit) = &callback.resources { - let mut map = resources.defaults; - - for &(label, units) in uninit.iter() { - let idx = match resources.labels.iter().position(|&l| l == label) { - Some(idx) => idx, - None => return Err(Error::ResourceNameNotFoundForMethod(label, method_name)), - }; - - // If resource capacity set to `0`, we ignore the unit value of the method - // and set it to `0` as well, effectively making the resource unlimited. - if resources.capacities[idx] == 0 { - map[idx] = 0; - } else { - map[idx] = units; - } - } - - callback.resources = MethodResources::Initialized(map); - } - } - - Ok(self) - } - /// Helper for obtaining a mut ref to the callbacks HashMap. fn mut_callbacks(&mut self) -> &mut FxHashMap<&'static str, MethodCallback> { Arc::make_mut(&mut self.callbacks) @@ -438,11 +356,11 @@ impl Methods { let response = match self.method(&req.method).map(|c| &c.callback) { None => MethodResponse::error(req.id, ErrorObject::from(ErrorCode::MethodNotFound)), Some(MethodKind::Sync(cb)) => (cb)(id, params, usize::MAX), - Some(MethodKind::Async(cb)) => (cb)(id.into_owned(), params.into_owned(), 0, usize::MAX, None).await, + Some(MethodKind::Async(cb)) => (cb)(id.into_owned(), params.into_owned(), 0, usize::MAX).await, Some(MethodKind::Subscription(cb)) => { let conn_state = ConnState { conn_id: 0, close_notify: close_notify.clone(), id_provider: &RandomIntegerIdProvider }; - let res = (cb)(id, params, sink.clone(), conn_state, None).await; + let res = (cb)(id, params, sink.clone(), conn_state).await; // This message is not used because it's used for metrics so we discard in other to // not read once this is used for subscriptions. @@ -562,22 +480,20 @@ impl RpcModule { &mut self, method_name: &'static str, callback: F, - ) -> Result + ) -> Result<&mut MethodCallback, Error> where Context: Send + Sync + 'static, R: Serialize, F: Fn(Params, &Context) -> Result + Send + Sync + 'static, { let ctx = self.ctx.clone(); - let callback = self.methods.verify_and_insert( + self.methods.verify_and_insert( method_name, MethodCallback::new_sync(Arc::new(move |id, params, max_response_size| match callback(params, &*ctx) { Ok(res) => MethodResponse::response(id, res, max_response_size), Err(err) => MethodResponse::error(id, err), })), - )?; - - Ok(MethodResourcesBuilder { build: ResourceVec::new(), callback }) + ) } /// Register a new asynchronous RPC method, which computes the response with the given callback. @@ -585,35 +501,28 @@ impl RpcModule { &mut self, method_name: &'static str, callback: Fun, - ) -> Result + ) -> Result<&mut MethodCallback, Error> where R: Serialize + Send + Sync + 'static, Fut: Future> + Send, Fun: (Fn(Params<'static>, Arc) -> Fut) + Clone + Send + Sync + 'static, { let ctx = self.ctx.clone(); - let callback = self.methods.verify_and_insert( + self.methods.verify_and_insert( method_name, - MethodCallback::new_async(Arc::new(move |id, params, _, max_response_size, claimed| { + MethodCallback::new_async(Arc::new(move |id, params, _, max_response_size| { let ctx = ctx.clone(); let callback = callback.clone(); let future = async move { - let result = match callback(params, ctx).await { + match callback(params, ctx).await { Ok(res) => MethodResponse::response(id, res, max_response_size), Err(err) => MethodResponse::error(id, err), - }; - - // Release claimed resources - drop(claimed); - - result + } }; future.boxed() })), - )?; - - Ok(MethodResourcesBuilder { build: ResourceVec::new(), callback }) + ) } /// Register a new **blocking** synchronous RPC method, which computes the response with the given callback. @@ -622,7 +531,7 @@ impl RpcModule { &mut self, method_name: &'static str, callback: F, - ) -> Result + ) -> Result<&mut MethodCallback, Error> where Context: Send + Sync + 'static, R: Serialize, @@ -631,20 +540,13 @@ impl RpcModule { let ctx = self.ctx.clone(); let callback = self.methods.verify_and_insert( method_name, - MethodCallback::new_async(Arc::new(move |id, params, _, max_response_size, claimed| { + MethodCallback::new_async(Arc::new(move |id, params, _, max_response_size| { let ctx = ctx.clone(); let callback = callback.clone(); - tokio::task::spawn_blocking(move || { - let result = match callback(params, ctx) { - Ok(result) => MethodResponse::response(id, result, max_response_size), - Err(err) => MethodResponse::error(id, err), - }; - - // Release claimed resources - drop(claimed); - - result + tokio::task::spawn_blocking(move || match callback(params, ctx) { + Ok(result) => MethodResponse::response(id, result, max_response_size), + Err(err) => MethodResponse::error(id, err), }) .map(|result| match result { Ok(r) => r, @@ -657,7 +559,7 @@ impl RpcModule { })), )?; - Ok(MethodResourcesBuilder { build: ResourceVec::new(), callback }) + Ok(callback) } /// Register a new publish/subscribe interface using JSON-RPC notifications. @@ -716,7 +618,7 @@ impl RpcModule { notif_method_name: &'static str, unsubscribe_method_name: &'static str, callback: F, - ) -> Result + ) -> Result<&mut MethodCallback, Error> where Context: Send + Sync + 'static, F: Fn(Params, SubscriptionSink, Arc) -> SubscriptionResult + Send + Sync + 'static, @@ -771,7 +673,7 @@ impl RpcModule { let callback = { self.methods.verify_and_insert( subscribe_method_name, - MethodCallback::new_subscription(Arc::new(move |id, params, method_sink, conn, claimed| { + MethodCallback::new_subscription(Arc::new(move |id, params, method_sink, conn| { let uniq_sub = SubscriptionKey { conn_id: conn.conn_id, sub_id: conn.id_provider.next_id() }; // response to the subscription call. @@ -785,7 +687,6 @@ impl RpcModule { uniq_sub, id: Some((id.clone().into_owned(), tx)), unsubscribe: None, - _claimed: claimed, }; // The callback returns a `SubscriptionResult` for better ergonomics and is not propagated further. @@ -807,7 +708,7 @@ impl RpcModule { )? }; - Ok(MethodResourcesBuilder { build: ResourceVec::new(), callback }) + Ok(callback) } /// Register an alias for an existing_method. Alias uniqueness is enforced. @@ -878,8 +779,6 @@ pub struct SubscriptionSink { id: Option<(Id<'static>, oneshot::Sender)>, /// Having some value means the subscription was accepted. unsubscribe: UnsubscribeCall, - /// Claimed resources. - _claimed: Option, } impl SubscriptionSink { @@ -1098,7 +997,7 @@ impl SubscriptionSink { } let msg = self.build_message(result)?; - self.inner.send_raw(msg).map_err(|e| SubscriptionSinkError::Send(e.into())) + self.inner.send_raw(msg).map_err(SubscriptionSinkError::Send) } fn is_active_subscription(&self) -> bool { diff --git a/server/src/server.rs b/server/src/server.rs index c254d6c5e4..eaca28d8d7 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -44,7 +44,6 @@ use jsonrpsee_core::id_providers::RandomIntegerIdProvider; use jsonrpsee_core::server::helpers::MethodResponse; use jsonrpsee_core::server::host_filtering::AllowHosts; -use jsonrpsee_core::server::resource_limiting::Resources; use jsonrpsee_core::server::rpc_module::Methods; use jsonrpsee_core::traits::IdProvider; use jsonrpsee_core::{http_helpers, Error, TEN_MB_SIZE_BYTES}; @@ -64,7 +63,6 @@ const MAX_CONNECTIONS: u32 = 100; pub struct Server { listener: TcpListener, cfg: Settings, - resources: Resources, logger: L, id_provider: Arc, service_builder: tower::ServiceBuilder, @@ -76,7 +74,6 @@ impl std::fmt::Debug for Server { .field("listener", &self.listener) .field("cfg", &self.cfg) .field("id_provider", &self.id_provider) - .field("resources", &self.resources) .finish() } } @@ -107,7 +104,7 @@ where /// /// This will run on the tokio runtime until the server is stopped or the `ServerHandle` is dropped. pub fn start(mut self, methods: impl Into) -> Result { - let methods = methods.into().initialize_resources(&self.resources)?; + let methods = methods.into(); let (stop_tx, stop_rx) = watch::channel(()); let stop_handle = StopHandle::new(stop_rx); @@ -125,7 +122,6 @@ where let max_response_body_size = self.cfg.max_response_body_size; let max_log_length = self.cfg.max_log_length; let allow_hosts = self.cfg.allow_hosts; - let resources = self.resources; let logger = self.logger; let batch_requests_supported = self.cfg.batch_requests_supported; let id_provider = self.id_provider; @@ -142,7 +138,6 @@ where remote_addr, methods: methods.clone(), allow_hosts: allow_hosts.clone(), - resources: resources.clone(), max_request_body_size, max_response_body_size, max_log_length, @@ -180,8 +175,6 @@ struct Settings { max_response_body_size: u32, /// Maximum number of incoming connections allowed. max_connections: u32, - /// Maximum number of subscriptions per connection. - max_subscriptions_per_connection: u32, /// Max length for logging for requests and responses /// /// Logs bigger than this limit will be truncated. @@ -208,7 +201,6 @@ impl Default for Settings { max_request_body_size: TEN_MB_SIZE_BYTES, max_response_body_size: TEN_MB_SIZE_BYTES, max_log_length: 4096, - max_subscriptions_per_connection: 1024, max_connections: MAX_CONNECTIONS, batch_requests_supported: true, allow_hosts: AllowHosts::Any, @@ -225,7 +217,6 @@ impl Default for Settings { #[derive(Debug)] pub struct Builder { settings: Settings, - resources: Resources, logger: L, id_provider: Arc, service_builder: tower::ServiceBuilder, @@ -235,7 +226,6 @@ impl Default for Builder { fn default() -> Self { Builder { settings: Settings::default(), - resources: Resources::default(), logger: (), id_provider: Arc::new(RandomIntegerIdProvider), service_builder: tower::ServiceBuilder::new(), @@ -276,22 +266,6 @@ impl Builder { self } - /// Set the maximum number of connections allowed. Default is 1024. - pub fn max_subscriptions_per_connection(mut self, max: u32) -> Self { - self.settings.max_subscriptions_per_connection = max; - self - } - - /// Register a new resource kind. Errors if `label` is already registered, or if the number of - /// registered resources on this server instance would exceed 8. - /// - /// See the module documentation for [`resource_limiting`](../jsonrpsee_utils/server/resource_limiting/index.html#resource-limiting) - /// for details. - pub fn register_resource(mut self, label: &'static str, capacity: u16, default: u16) -> Result { - self.resources.register(label, capacity, default)?; - Ok(self) - } - /// Add a logger to the builder [`Logger`](../jsonrpsee_core/logger/trait.Logger.html). /// /// ``` @@ -336,7 +310,6 @@ impl Builder { pub fn set_logger(self, logger: T) -> Builder { Builder { settings: self.settings, - resources: self.resources, logger, id_provider: self.id_provider, service_builder: self.service_builder, @@ -426,13 +399,7 @@ impl Builder { /// } /// ``` pub fn set_middleware(self, service_builder: tower::ServiceBuilder) -> Builder { - Builder { - settings: self.settings, - resources: self.resources, - logger: self.logger, - id_provider: self.id_provider, - service_builder, - } + Builder { settings: self.settings, logger: self.logger, id_provider: self.id_provider, service_builder } } /// Configure the server to only serve JSON-RPC HTTP requests. @@ -485,7 +452,6 @@ impl Builder { Ok(Server { listener, cfg: self.settings, - resources: self.resources, logger: self.logger, id_provider: self.id_provider, service_builder: self.service_builder, @@ -521,7 +487,6 @@ impl Builder { Ok(Server { listener, cfg: self.settings, - resources: self.resources, logger: self.logger, id_provider: self.id_provider, service_builder: self.service_builder, @@ -559,8 +524,6 @@ pub(crate) struct ServiceData { pub(crate) methods: Methods, /// Access control. pub(crate) allow_hosts: AllowHosts, - /// Tracker for currently used resources on the server. - pub(crate) resources: Resources, /// Max request body size. pub(crate) max_request_body_size: u32, /// Max response body size. @@ -674,7 +637,6 @@ impl hyper::service::Service> for TowerSe // The request wasn't an upgrade request; let's treat it as a standard HTTP request: let data = http::HandleRequest { methods: self.inner.methods.clone(), - resources: self.inner.resources.clone(), max_request_body_size: self.inner.max_request_body_size, max_response_body_size: self.inner.max_response_body_size, max_log_length: self.inner.max_log_length, @@ -750,8 +712,6 @@ struct ProcessConnection { methods: Methods, /// Access control. allow_hosts: AllowHosts, - /// Tracker for currently used resources on the server. - resources: Resources, /// Max request body size. max_request_body_size: u32, /// Max response body size. @@ -825,7 +785,6 @@ fn process_connection<'a, L: Logger, B, U>( remote_addr: cfg.remote_addr, methods: cfg.methods, allow_hosts: cfg.allow_hosts, - resources: cfg.resources, max_request_body_size: cfg.max_request_body_size, max_response_body_size: cfg.max_response_body_size, max_log_length: cfg.max_log_length, diff --git a/server/src/transport/http.rs b/server/src/transport/http.rs index ad693bd708..9337b026ac 100644 --- a/server/src/transport/http.rs +++ b/server/src/transport/http.rs @@ -11,7 +11,7 @@ use jsonrpsee_core::error::GenericTransportError; use jsonrpsee_core::http_helpers::read_body; use jsonrpsee_core::server::helpers::{prepare_error, BatchResponse, BatchResponseBuilder, MethodResponse}; use jsonrpsee_core::server::rpc_module::MethodKind; -use jsonrpsee_core::server::{resource_limiting::Resources, rpc_module::Methods}; +use jsonrpsee_core::server::rpc_module::Methods; use jsonrpsee_core::tracing::{rx_log_from_json, tx_log_from_str}; use jsonrpsee_core::JsonRawValue; use jsonrpsee_types::error::{ErrorCode, BATCHES_NOT_SUPPORTED_CODE, BATCHES_NOT_SUPPORTED_MSG}; @@ -51,7 +51,6 @@ pub(crate) struct ProcessValidatedRequest<'a, L: Logger> { pub(crate) request: hyper::Request, pub(crate) logger: &'a L, pub(crate) methods: Methods, - pub(crate) resources: Resources, pub(crate) max_request_body_size: u32, pub(crate) max_response_body_size: u32, pub(crate) max_log_length: u32, @@ -67,7 +66,6 @@ pub(crate) async fn process_validated_request( request, logger, methods, - resources, max_request_body_size, max_response_body_size, max_log_length, @@ -89,15 +87,8 @@ pub(crate) async fn process_validated_request( // Single request or notification if is_single { - let call = CallData { - conn_id: 0, - logger, - methods: &methods, - max_response_body_size, - max_log_length, - resources: &resources, - request_start, - }; + let call = + CallData { conn_id: 0, logger, methods: &methods, max_response_body_size, max_log_length, request_start }; let response = process_single_request(body, call).await; logger.on_response(&response.result, request_start, TransportProtocol::Http); response::ok_response(response.result) @@ -121,7 +112,6 @@ pub(crate) async fn process_validated_request( methods: &methods, max_response_body_size, max_log_length, - resources: &resources, request_start, }, }) @@ -144,7 +134,6 @@ pub(crate) struct CallData<'a, L: Logger> { methods: &'a Methods, max_response_body_size: u32, max_log_length: u32, - resources: &'a Resources, request_start: L::Instant, } @@ -221,7 +210,7 @@ pub(crate) async fn execute_call_with_tracing<'a, L: Logger>( } pub(crate) async fn execute_call(req: Request<'_>, call: CallData<'_, L>) -> MethodResponse { - let CallData { resources, methods, logger, max_response_body_size, max_log_length, conn_id, request_start } = call; + let CallData { methods, logger, max_response_body_size, max_log_length, conn_id, request_start } = call; rx_log_from_json(&req, call.max_log_length); @@ -237,33 +226,15 @@ pub(crate) async fn execute_call(req: Request<'_>, call: CallData<'_, Some((name, method)) => match &method.inner() { MethodKind::Sync(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::MethodCall, TransportProtocol::Http); - - match method.claim(name, resources) { - Ok(guard) => { - let r = (callback)(id, params, max_response_body_size as usize); - drop(guard); - r - } - Err(err) => { - tracing::error!("[Methods::execute_with_resources] failed to lock resources: {}", err); - MethodResponse::error(id, ErrorObject::from(ErrorCode::ServerIsBusy)) - } - } + (callback)(id, params, max_response_body_size as usize) } MethodKind::Async(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::MethodCall, TransportProtocol::Http); - match method.claim(name, resources) { - Ok(guard) => { - let id = id.into_owned(); - let params = params.into_owned(); - - (callback)(id, params, conn_id, max_response_body_size as usize, Some(guard)).await - } - Err(err) => { - tracing::error!("[Methods::execute_with_resources] failed to lock resources: {}", err); - MethodResponse::error(id, ErrorObject::from(ErrorCode::ServerIsBusy)) - } - } + + let id = id.into_owned(); + let params = params.into_owned(); + + (callback)(id, params, conn_id, max_response_body_size as usize).await } MethodKind::Subscription(_) | MethodKind::Unsubscription(_) => { logger.on_call(name, params.clone(), logger::MethodKind::Unknown, TransportProtocol::Http); @@ -288,7 +259,6 @@ fn execute_notification(notif: Notif, max_log_length: u32) -> MethodResponse { pub(crate) struct HandleRequest { pub(crate) methods: Methods, - pub(crate) resources: Resources, pub(crate) max_request_body_size: u32, pub(crate) max_response_body_size: u32, pub(crate) max_log_length: u32, @@ -304,7 +274,6 @@ pub(crate) async fn handle_request( ) -> hyper::Response { let HandleRequest { methods, - resources, max_request_body_size, max_response_body_size, max_log_length, @@ -322,7 +291,6 @@ pub(crate) async fn handle_request( process_validated_request(ProcessValidatedRequest { request, methods, - resources, max_request_body_size, max_response_body_size, max_log_length, diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 0a1209e342..d1b4963d27 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -14,7 +14,6 @@ use futures_util::stream::FuturesOrdered; use futures_util::{Future, FutureExt, StreamExt}; use hyper::upgrade::Upgraded; use jsonrpsee_core::server::helpers::{prepare_error, BatchResponse, BatchResponseBuilder, MethodResponse, MethodSink}; -use jsonrpsee_core::server::resource_limiting::Resources; use jsonrpsee_core::server::rpc_module::{ConnState, MethodKind, Methods, SendError}; use jsonrpsee_core::tracing::{rx_log_from_json, tx_log_from_str}; use jsonrpsee_core::traits::IdProvider; @@ -61,7 +60,6 @@ pub(crate) struct CallData<'a, L: Logger> { pub(crate) methods: &'a Methods, pub(crate) max_response_body_size: u32, pub(crate) max_log_length: u32, - pub(crate) resources: &'a Resources, pub(crate) sink: &'a MethodSink, pub(crate) logger: &'a L, pub(crate) request_start: L::Instant, @@ -173,7 +171,6 @@ pub(crate) async fn execute_call_with_tracing<'a, L: Logger>(req: Request<'a>, c /// Otherwise `(MethodResponse, Some(PendingSubscriptionCallTx)`. pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData<'_, L>) -> MethodResult { let CallData { - resources, methods, max_response_body_size, max_log_length, @@ -200,51 +197,23 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData Some((name, method)) => match &method.inner() { MethodKind::Sync(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::MethodCall, TransportProtocol::WebSocket); - match method.claim(name, resources) { - Ok(guard) => { - let r = (callback)(id, params, max_response_body_size as usize); - drop(guard); - MethodResult::SendAndLogger(r) - } - Err(err) => { - tracing::error!("[Methods::execute_with_resources] failed to lock resources: {}", err); - let response = MethodResponse::error(id, ErrorObject::from(ErrorCode::ServerIsBusy)); - MethodResult::SendAndLogger(response) - } - } + MethodResult::SendAndLogger((callback)(id, params, max_response_body_size as usize)) } MethodKind::Async(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::MethodCall, TransportProtocol::WebSocket); - match method.claim(name, resources) { - Ok(guard) => { - let id = id.into_owned(); - let params = params.into_owned(); - - let response = - (callback)(id, params, conn_id, max_response_body_size as usize, Some(guard)).await; - MethodResult::SendAndLogger(response) - } - Err(err) => { - tracing::error!("[Methods::execute_with_resources] failed to lock resources: {}", err); - let response = MethodResponse::error(id, ErrorObject::from(ErrorCode::ServerIsBusy)); - MethodResult::SendAndLogger(response) - } - } + + let id = id.into_owned(); + let params = params.into_owned(); + + let response = (callback)(id, params, conn_id, max_response_body_size as usize).await; + MethodResult::SendAndLogger(response) } MethodKind::Subscription(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::Subscription, TransportProtocol::WebSocket); - match method.claim(name, resources) { - Ok(guard) => { - let conn_state = ConnState { conn_id, close_notify, id_provider }; - let response = callback(id.clone(), params, sink.clone(), conn_state, Some(guard)).await; - MethodResult::JustLogger(response) - } - Err(err) => { - tracing::error!("[Methods::execute_with_resources] failed to lock resources: {}", err); - let response = MethodResponse::error(id, ErrorObject::from(ErrorCode::ServerIsBusy)); - MethodResult::SendAndLogger(response) - } - } + + let conn_state = ConnState { conn_id, close_notify, id_provider }; + let response = callback(id.clone(), params, sink.clone(), conn_state).await; + MethodResult::JustLogger(response) } MethodKind::Unsubscription(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::Unsubscription, TransportProtocol::WebSocket); @@ -270,7 +239,6 @@ pub(crate) async fn background_task( ) -> Result<(), Error> { let ServiceData { methods, - resources, max_request_body_size, max_response_body_size, max_log_length, @@ -362,7 +330,6 @@ pub(crate) async fn background_task( Some(b'{') => { let data = std::mem::take(&mut data); let mut sink = sink.clone(); - let resources = &resources; let methods = &methods; let id_provider = &*id_provider; let close_notify = close_notify.clone(); @@ -370,7 +337,6 @@ pub(crate) async fn background_task( let fut = async move { let call = CallData { conn_id: conn_id as usize, - resources, max_response_body_size, max_log_length, methods, @@ -411,7 +377,6 @@ pub(crate) async fn background_task( } Some(b'[') => { // Make sure the following variables are not moved into async closure below. - let resources = &resources; let methods = &methods; let mut sink = sink.clone(); let id_provider = id_provider.clone(); @@ -423,7 +388,6 @@ pub(crate) async fn background_task( data, call: CallData { conn_id: conn_id as usize, - resources, max_response_body_size, max_log_length, methods, diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 54f47327ef..2ca8fb1638 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -744,7 +744,8 @@ async fn http_batch_works() { assert_eq!(err_responses, vec![&ErrorObject::borrowed(UNKNOWN_ERROR_CODE, &"Custom error: err", None)]); } -#[tokio::test] +// re-write test with backpressure. +/*#[tokio::test] async fn ws_server_limit_subs_per_conn_works() { use futures::StreamExt; use jsonrpsee::types::error::{CallError, TOO_MANY_SUBSCRIPTIONS_CODE, TOO_MANY_SUBSCRIPTIONS_MSG}; @@ -864,7 +865,7 @@ async fn ws_server_unsub_methods_should_ignore_sub_limit() { // This should not hit any limits, and unsubscription should have worked: assert!(res.is_ok(), "Unsubscription method was successfully called"); assert!(res.unwrap(), "Unsubscription was successful"); -} +}*/ #[tokio::test] async fn http_unsupported_methods_dont_work() { diff --git a/tests/tests/metrics.rs b/tests/tests/metrics.rs index 8baf5d4f90..c5d7e32afa 100644 --- a/tests/tests/metrics.rs +++ b/tests/tests/metrics.rs @@ -113,12 +113,7 @@ fn test_module() -> RpcModule<()> { } async fn websocket_server(module: RpcModule<()>, counter: Counter) -> Result<(SocketAddr, ServerHandle), Error> { - let server = ServerBuilder::default() - .register_resource("CPU", 6, 2)? - .register_resource("MEM", 10, 1)? - .set_logger(counter) - .build("127.0.0.1:0") - .await?; + let server = ServerBuilder::default().set_logger(counter).build("127.0.0.1:0").await?; let addr = server.local_addr()?; let handle = server.start(module)?; @@ -127,12 +122,7 @@ async fn websocket_server(module: RpcModule<()>, counter: Counter) -> Result<(So } async fn http_server(module: RpcModule<()>, counter: Counter) -> Result<(SocketAddr, ServerHandle), Error> { - let server = ServerBuilder::default() - .register_resource("CPU", 6, 2)? - .register_resource("MEM", 10, 1)? - .set_logger(counter) - .build("127.0.0.1:0") - .await?; + let server = ServerBuilder::default().set_logger(counter).build("127.0.0.1:0").await?; let addr = server.local_addr()?; let handle = server.start(module)?; diff --git a/tests/tests/resource_limiting.rs b/tests/tests/resource_limiting.rs deleted file mode 100644 index 27650490f5..0000000000 --- a/tests/tests/resource_limiting.rs +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2019-2021 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any -// person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the -// Software without restriction, including without -// limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software -// is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice -// shall be included in all copies or substantial portions -// of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use std::net::SocketAddr; -use std::time::Duration; - -use futures::StreamExt; -use jsonrpsee::core::client::{ClientT, SubscriptionClientT}; -use jsonrpsee::core::params::ArrayParams; -use jsonrpsee::core::Error; -use jsonrpsee::http_client::HttpClientBuilder; -use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::{ServerBuilder, ServerHandle}; -use jsonrpsee::types::error::CallError; -use jsonrpsee::types::SubscriptionResult; -use jsonrpsee::ws_client::WsClientBuilder; -use jsonrpsee::{rpc_params, RpcModule, SubscriptionSink}; -use tokio::time::{interval, sleep}; -use tokio_stream::wrappers::IntervalStream; - -fn module_manual() -> Result, Error> { - let mut module = RpcModule::new(()); - - module.register_async_method("say_hello", |_, _| async move { - sleep(Duration::from_millis(50)).await; - Ok("hello") - })?; - - module - .register_async_method("expensive_call", |_, _| async move { - sleep(Duration::from_millis(50)).await; - Ok("hello expensive call") - })? - .resource("CPU", 3)?; - - module - .register_async_method("memory_hog", |_, _| async move { - sleep(Duration::from_millis(50)).await; - Ok("hello memory hog") - })? - .resource("CPU", 0)? - .resource("MEM", 8)?; - - // Drop the `SubscriptionSink` to cause the internal `ResourceGuard` allocated per subscription call - // to get dropped. This is the equivalent of not having any resource limits (ie, sink is never used). - module - .register_subscription("subscribe_hello", "s_hello", "unsubscribe_hello", move |_, mut sink, _| { - sink.accept()?; - Ok(()) - })? - .resource("SUB", 3)?; - - // Keep the `SubscriptionSink` alive for a bit to validate that `ResourceGuard` is alive - // and the subscription method gets limited. - module - .register_subscription("subscribe_hello_limit", "s_hello", "unsubscribe_hello_limit", move |_, mut sink, _| { - tokio::spawn(async move { - for val in 0..10 { - // Sink is accepted on the first `send` call. - sink.send(&val).unwrap(); - sleep(Duration::from_secs(1)).await; - } - }); - - Ok(()) - })? - .resource("SUB", 3)?; - - Ok(module) -} - -fn module_macro() -> RpcModule<()> { - #[rpc(server)] - pub trait Rpc { - #[method(name = "say_hello")] - async fn hello(&self) -> Result<&'static str, Error> { - sleep(Duration::from_millis(50)).await; - Ok("hello") - } - - #[method(name = "expensive_call", resources("CPU" = 3))] - async fn expensive(&self) -> Result<&'static str, Error> { - sleep(Duration::from_millis(50)).await; - Ok("hello expensive call") - } - - #[method(name = "memory_hog", resources("CPU" = 0, "MEM" = 8))] - async fn memory(&self) -> Result<&'static str, Error> { - sleep(Duration::from_millis(50)).await; - Ok("hello memory hog") - } - - #[subscription(name = "subscribe_hello", item = String, resources("SUB" = 3))] - fn sub_hello(&self); - - #[subscription(name = "subscribe_hello_limit", item = String, resources("SUB" = 3))] - fn sub_hello_limit(&self); - } - - impl RpcServer for () { - fn sub_hello(&self, mut sink: SubscriptionSink) -> SubscriptionResult { - sink.accept()?; - Ok(()) - } - - fn sub_hello_limit(&self, mut sink: SubscriptionSink) -> SubscriptionResult { - tokio::spawn(async move { - let interval = interval(Duration::from_secs(1)); - let stream = IntervalStream::new(interval).map(move |_| 1); - - sink.pipe_from_stream(stream).await; - }); - - Ok(()) - } - } - - ().into_rpc() -} - -async fn websocket_server(module: RpcModule<()>) -> Result<(SocketAddr, ServerHandle), Error> { - let server = ServerBuilder::default() - .register_resource("CPU", 6, 2)? - .register_resource("MEM", 10, 1)? - .register_resource("SUB", 6, 1)? - .build("127.0.0.1:0") - .await?; - - let addr = server.local_addr()?; - let handle = server.start(module)?; - - Ok((addr, handle)) -} - -async fn http_server(module: RpcModule<()>) -> Result<(SocketAddr, ServerHandle), Error> { - let server = ServerBuilder::default() - .register_resource("CPU", 6, 2)? - .register_resource("MEM", 10, 1)? - .register_resource("SUB", 6, 1)? - .build("127.0.0.1:0") - .await?; - - let addr = server.local_addr()?; - let handle = server.start(module)?; - - Ok((addr, handle)) -} - -fn assert_server_busy(fail: Result) { - match fail { - Err(Error::Call(CallError::Custom(err))) => { - assert_eq!(err.code(), -32604); - assert_eq!(err.message(), "Server is busy, try again later"); - } - fail => panic!("Expected error, got: {:?}", fail), - } -} - -async fn run_tests_on_ws_server(server_addr: SocketAddr, server_handle: ServerHandle) { - let server_url = format!("ws://{}", server_addr); - let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - - // 2 CPU units (default) per call, so 4th call exceeds cap - let (pass1, pass2, pass3, fail) = tokio::join!( - client.request::("say_hello", rpc_params!()), - client.request::("say_hello", rpc_params![]), - client.request::("say_hello", rpc_params![]), - client.request::("say_hello", rpc_params![]), - ); - - assert!(pass1.is_ok()); - assert!(pass2.is_ok()); - assert!(pass3.is_ok()); - assert_server_busy(fail); - - // 3 CPU units per call, so 3rd call exceeds CPU cap, but we can still get on MEM - let (pass_cpu1, pass_cpu2, fail_cpu, pass_mem, fail_mem) = tokio::join!( - client.request::("expensive_call", rpc_params![]), - client.request::("expensive_call", rpc_params![]), - client.request::("expensive_call", rpc_params![]), - client.request::("memory_hog", rpc_params![]), - client.request::("memory_hog", rpc_params![]), - ); - - assert!(pass_cpu1.is_ok()); - assert!(pass_cpu2.is_ok()); - assert_server_busy(fail_cpu); - assert!(pass_mem.is_ok()); - assert_server_busy(fail_mem); - - // If we issue multiple subscription requests at the same time from the same client, - // but the subscriptions drop their sinks when the subscription has been accepted or rejected. - // - // Thus, we can't assume that all subscriptions drop their resources instantly anymore. - let (pass1, pass2) = tokio::join!( - client.subscribe::("subscribe_hello", rpc_params![], "unsubscribe_hello"), - client.subscribe::("subscribe_hello", rpc_params![], "unsubscribe_hello"), - ); - - assert!(pass1.is_ok()); - assert!(pass2.is_ok()); - - // 3 CPU units (manually set for subscriptions) per call, so 3th call exceeds cap - let (pass1, pass2, fail) = tokio::join!( - client.subscribe::("subscribe_hello_limit", rpc_params![], "unsubscribe_hello_limit"), - client.subscribe::("subscribe_hello_limit", rpc_params![], "unsubscribe_hello_limit"), - client.subscribe::("subscribe_hello_limit", rpc_params![], "unsubscribe_hello_limit"), - ); - - assert!(pass1.is_ok()); - assert!(pass2.is_ok()); - assert_server_busy(fail); - - server_handle.stop().unwrap(); - server_handle.stopped().await; -} - -async fn run_tests_on_http_server(server_addr: SocketAddr, server_handle: ServerHandle) { - let server_url = format!("http://{}", server_addr); - let client = HttpClientBuilder::default().build(&server_url).unwrap(); - - // 2 CPU units (default) per call, so 4th call exceeds cap - let (a, b, c, d) = tokio::join!( - client.request::("say_hello", rpc_params![]), - client.request::("say_hello", rpc_params![]), - client.request::("say_hello", rpc_params![]), - client.request::("say_hello", rpc_params![]), - ); - - // HTTP does not guarantee ordering - let mut passes = 0; - - for result in [a, b, c, d] { - if result.is_ok() { - passes += 1; - } else { - assert_server_busy(result); - } - } - - assert_eq!(passes, 3); - - server_handle.stop().unwrap(); - server_handle.stopped().await; -} - -#[tokio::test] -async fn ws_server_with_manual_module() { - let (server_addr, server_handle) = websocket_server(module_manual().unwrap()).await.unwrap(); - - run_tests_on_ws_server(server_addr, server_handle).await; -} - -#[tokio::test] -async fn ws_server_with_macro_module() { - let (server_addr, server_handle) = websocket_server(module_macro()).await.unwrap(); - - run_tests_on_ws_server(server_addr, server_handle).await; -} - -#[tokio::test] -async fn http_server_with_manual_module() { - let (server_addr, server_handle) = http_server(module_manual().unwrap()).await.unwrap(); - - run_tests_on_http_server(server_addr, server_handle).await; -} - -#[tokio::test] -async fn http_server_with_macro_module() { - let (server_addr, server_handle) = http_server(module_macro()).await.unwrap(); - - run_tests_on_http_server(server_addr, server_handle).await; -} From cfa48ee030a4501e155d90e7949fa7edcfd18b4e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 4 Jan 2023 12:46:11 +0100 Subject: [PATCH 04/60] kill connection once message tx fails --- server/src/server.rs | 2 +- server/src/transport/ws.rs | 14 +++++++++++--- tests/tests/integration_tests.rs | 3 --- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/server/src/server.rs b/server/src/server.rs index eaca28d8d7..0b8ab3f2d6 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -424,7 +424,7 @@ impl Builder { /// Configure the max number of messages that can be buffered /// - /// If this limit is exceeded the message can be dropped or the connection could be closed. + /// If this limit is exceeded the connection will be closed. pub fn set_buffer_size(mut self, c: u32) -> Self { self.settings.buffer_capacity = c; self diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index d1b4963d27..98637f7570 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -269,6 +269,11 @@ pub(crate) async fn background_task( let result = loop { data.clear(); + // We close down the sink if any message transmission fails and close down the connection. + if sink.is_closed() { + return Err(Error::Custom("Connection buffer limit exceeded".to_string())); + } + { // Need the extra scope to drop this pinned future and reclaim access to `data` let receive = async { @@ -353,8 +358,9 @@ pub(crate) async fn background_task( } MethodResult::SendAndLogger(r) => { logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); - // TODO: close conn?!. - if let Err(_) = sink.send_raw(r.result) {} + if sink.send_raw(r.result).is_err() { + sink.close(); + } } }; } @@ -403,7 +409,9 @@ pub(crate) async fn background_task( if let Some(response) = response { tx_log_from_str(&response.result, max_log_length); logger.on_response(&response.result, request_start, TransportProtocol::WebSocket); - let _ = sink.send_raw(response.result); + if sink.send_raw(response.result).is_err() { + sink.close(); + } } }; diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 2ca8fb1638..78378c7b0f 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -39,7 +39,6 @@ use helpers::{ }; use hyper::http::HeaderValue; use jsonrpsee::core::client::{ClientT, IdKind, Subscription, SubscriptionClientT}; -use jsonrpsee::core::error::SubscriptionClosed; use jsonrpsee::core::params::{ArrayParams, BatchRequestBuilder}; use jsonrpsee::core::server::rpc_module::{SendError, SubscriptionSinkError}; use jsonrpsee::core::{Error, JsonValue}; @@ -47,8 +46,6 @@ use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::rpc_params; use jsonrpsee::types::error::{ErrorObject, UNKNOWN_ERROR_CODE}; use jsonrpsee::ws_client::WsClientBuilder; -use tokio::time::interval; -use tokio_stream::wrappers::IntervalStream; use tower_http::cors::CorsLayer; #[tokio::test] From e103908b36b6b83aa661cc70d7014a250be6675f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 5 Jan 2023 12:39:38 +0100 Subject: [PATCH 05/60] switch to tokio::mpsc --- core/src/error.rs | 18 +++---------- core/src/server/helpers.rs | 14 +++------- core/src/server/rpc_module.rs | 27 ++++++++++--------- server/src/transport/ws.rs | 50 +++++++++++++++++------------------ 4 files changed, 45 insertions(+), 64 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index 2614aef564..e88253eb0e 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -111,21 +111,6 @@ pub enum Error { /// Access control verification of HTTP headers failed. #[error("HTTP header: `{0}` value: `{1}` verification failed")] HttpHeaderRejected(&'static str, String), - /// Failed to execute a method because a resource was already at capacity - #[error("Resource at capacity: {0}")] - ResourceAtCapacity(&'static str), - /// Failed to register a resource due to a name conflict - #[error("Resource name already taken: {0}")] - ResourceNameAlreadyTaken(&'static str), - /// Failed to initialize resources for a method at startup - #[error("Resource name `{0}` not found for method `{1}`")] - ResourceNameNotFoundForMethod(&'static str, &'static str), - /// Trying to claim resources for a method execution, but the method resources have not been initialized - #[error("Method `{0}` has uninitialized resources")] - UninitializedMethod(Box), - /// Failed to register a resource due to a maximum number of resources already registered - #[error("Maximum number of resources reached")] - MaxResourcesReached, /// Custom error. #[error("Custom error: {0}")] Custom(String), @@ -135,6 +120,9 @@ pub enum Error { /// Empty batch request. #[error("Empty batch request is not allowed")] EmptyBatchRequest, + /// Max buffer capacity exceeded. + #[error("Max connection buffer capacity exceeded.")] + ConnectionBufferExceeded, } impl Error { diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index 24bb18c701..780cd75377 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -26,14 +26,13 @@ use std::io; +use crate::server::rpc_module::SendError; use crate::tracing::tx_log_from_str; use crate::Error; -use futures_channel::mpsc; use jsonrpsee_types::error::{ErrorCode, ErrorObject, ErrorResponse, OVERSIZED_RESPONSE_CODE, OVERSIZED_RESPONSE_MSG}; use jsonrpsee_types::{Id, InvalidRequest, Response}; use serde::Serialize; - -use super::rpc_module::SendError; +use tokio::sync::mpsc; /// Bounded writer that allows writing at most `max_len` bytes. /// @@ -84,7 +83,7 @@ impl<'a> io::Write for &'a mut BoundedWriter { #[derive(Clone, Debug)] pub struct MethodSink { /// Channel sender. - tx: mpsc::Sender, + pub tx: mpsc::Sender, /// Max response size in bytes for a executed call. max_response_size: u32, /// Max log length. @@ -111,8 +110,6 @@ impl MethodSink { pub fn send_error(&mut self, id: Id, error: ErrorObject) -> Result<(), SendError> { let json = serde_json::to_string(&ErrorResponse::borrowed(error, id)).expect("valid JSON; qed"); - tx_log_from_str(&json, self.max_log_length); - self.send_raw(json) } @@ -128,11 +125,6 @@ impl MethodSink { self.tx.try_send(json).map_err(Into::into) } - /// Close the channel for any further messages. - pub fn close(&mut self) { - self.tx.close_channel(); - } - /// Get the maximum number of permitted subscriptions. pub const fn max_response_size(&self) -> u32 { self.max_response_size diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 6c47609ff2..01e200cc82 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -34,8 +34,7 @@ use crate::error::{Error, SubscriptionClosed}; use crate::id_providers::RandomIntegerIdProvider; use crate::server::helpers::MethodSink; use crate::traits::{IdProvider, ToRpcParams}; -use futures_channel::mpsc::TrySendError; -use futures_channel::{mpsc, oneshot}; +use futures_channel::oneshot; use futures_util::future::Either; use futures_util::pin_mut; use futures_util::{future::BoxFuture, FutureExt, Stream, StreamExt, TryStream, TryStreamExt}; @@ -48,10 +47,11 @@ use jsonrpsee_types::{ ErrorResponse, Id, Params, Request, Response, SubscriptionId as RpcSubscriptionId, SubscriptionPayload, SubscriptionResponse, SubscriptionResult, }; +use mpsc::error::TrySendError; use parking_lot::Mutex; use rustc_hash::FxHashMap; use serde::{de::DeserializeOwned, Serialize}; -use tokio::sync::{watch, Notify}; +use tokio::sync::{mpsc, watch, Notify}; use super::helpers::MethodResponse; @@ -366,7 +366,7 @@ impl Methods { // not read once this is used for subscriptions. // // The same information is part of `res` above. - let _ = rx_sink.next().await.expect("Every call must at least produce one response; qed"); + let _ = rx_sink.recv().await.expect("Every call must at least produce one response; qed"); res } @@ -742,10 +742,9 @@ pub enum SendError { impl From> for SendError { fn from(err: TrySendError) -> Self { - if err.is_full() { - Self::Full - } else { - Self::Disconnected + match err { + TrySendError::Closed(_) => Self::Disconnected, + TrySendError::Full(_) => Self::Full, } } } @@ -788,7 +787,7 @@ impl SubscriptionSink { let err = MethodResponse::error(id, err.into()); - self.answer_subscription(err, subscribe_call)?; + self.answer_subscription(err, subscribe_call).unwrap(); Ok(()) } @@ -801,7 +800,7 @@ impl SubscriptionSink { let response = MethodResponse::response(id, &self.uniq_sub.sub_id, self.inner.max_response_size() as usize); let success = response.success; - self.answer_subscription(response, subscribe_call)?; + self.answer_subscription(response, subscribe_call).unwrap(); if success { let (tx, rx) = watch::channel(()); @@ -837,9 +836,11 @@ impl SubscriptionSink { match self.accept() { Ok(_) => (), Err(SubscriptionAcceptRejectError::AlreadyCalled) => (), - Err(SubscriptionAcceptRejectError::Full) => return Err(SubscriptionSinkError::Send(SendError::Full)), + Err(SubscriptionAcceptRejectError::Full) => { + return Err(SubscriptionSinkError::Send(SendError::Full)); + } Err(SubscriptionAcceptRejectError::RemotePeerAborted) => { - return Err(SubscriptionSinkError::Send(SendError::Disconnected)) + return Err(SubscriptionSinkError::Send(SendError::Disconnected)); } }; @@ -1120,7 +1121,7 @@ impl Subscription { tracing::debug!("[Subscription::next] Closed."); return None; } - let raw = self.rx.next().await?; + let raw = self.rx.recv().await?; tracing::debug!("[Subscription::next]: rx {}", raw); let res = match serde_json::from_str::>(&raw) { diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 98637f7570..c592d22910 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -1,4 +1,5 @@ use std::pin::Pin; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; @@ -7,7 +8,6 @@ use crate::future::{FutureDriver, StopHandle}; use crate::logger::{self, Logger, TransportProtocol}; use crate::server::{MethodResult, ServiceData}; -use futures_channel::mpsc; use futures_util::future::{self, Either}; use futures_util::io::{BufReader, BufWriter}; use futures_util::stream::FuturesOrdered; @@ -24,8 +24,8 @@ use jsonrpsee_types::error::{ use jsonrpsee_types::{ErrorObject, Id, InvalidRequest, Notification, Params, Request}; use soketto::connection::Error as SokettoError; use soketto::data::ByteSlice125; -use tokio::sync::Notify; -use tokio_stream::wrappers::IntervalStream; +use tokio::sync::{mpsc, Notify}; +use tokio_stream::wrappers::{IntervalStream, ReceiverStream}; use tokio_util::compat::Compat; use tracing::instrument; @@ -257,6 +257,7 @@ pub(crate) async fn background_task( let (tx, rx) = mpsc::channel::(buffer_capacity as usize); let mut sink = MethodSink::new_with_limit(tx, max_response_body_size, max_log_length); let close_notify = Arc::new(Notify::new()); + let buf_exceeded = Arc::new(AtomicBool::new(false)); // Spawn another task that sends out the responses on the Websocket. tokio::spawn(send_task(rx, sender, stop_handle.clone(), ping_interval)); @@ -269,9 +270,9 @@ pub(crate) async fn background_task( let result = loop { data.clear(); - // We close down the sink if any message transmission fails and close down the connection. - if sink.is_closed() { - return Err(Error::Custom("Connection buffer limit exceeded".to_string())); + // We close down the server is the connection buffer of messages has been exceeded. + if buf_exceeded.load(Ordering::Relaxed) { + break Err(Error::ConnectionBufferExceeded); } { @@ -307,10 +308,8 @@ pub(crate) async fn background_task( ); if let Err(e) = sink.send_error(Id::Null, reject_too_big_request(max_request_body_size)) { match e { - SendError::Full => { - return Err(Error::Custom("Server buffer capacity exceeded".to_string())) - } - SendError::Disconnected => return Ok(()), + SendError::Full => break Err(Error::ConnectionBufferExceeded), + SendError::Disconnected => break Ok(()), } } continue; @@ -338,6 +337,7 @@ pub(crate) async fn background_task( let methods = &methods; let id_provider = &*id_provider; let close_notify = close_notify.clone(); + let buf_exceeded = buf_exceeded.clone(); let fut = async move { let call = CallData { @@ -358,8 +358,8 @@ pub(crate) async fn background_task( } MethodResult::SendAndLogger(r) => { logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); - if sink.send_raw(r.result).is_err() { - sink.close(); + if let Err(SendError::Full) = sink.send_raw(r.result) { + buf_exceeded.store(true, Ordering::Relaxed); } } }; @@ -376,8 +376,8 @@ pub(crate) async fn background_task( logger.on_response(&response.result, request_start, TransportProtocol::WebSocket); if let Err(e) = sink.send_raw(response.result) { match e { - SendError::Full => return Err(Error::Custom("Server buffer capacity exceeded".to_string())), - SendError::Disconnected => return Ok(()), + SendError::Full => break Err(Error::ConnectionBufferExceeded), + SendError::Disconnected => break Ok(()), } } } @@ -388,6 +388,7 @@ pub(crate) async fn background_task( let id_provider = id_provider.clone(); let data = std::mem::take(&mut data); let close_notify = close_notify.clone(); + let buf_exceeded = buf_exceeded.clone(); let fut = async move { let response = process_batch_request(Batch { @@ -409,8 +410,8 @@ pub(crate) async fn background_task( if let Some(response) = response { tx_log_from_str(&response.result, max_log_length); logger.on_response(&response.result, request_start, TransportProtocol::WebSocket); - if sink.send_raw(response.result).is_err() { - sink.close(); + if let Err(SendError::Full) = sink.send_raw(response.result) { + buf_exceeded.store(true, Ordering::Relaxed); } } }; @@ -420,8 +421,8 @@ pub(crate) async fn background_task( _ => { if let Err(e) = sink.send_error(Id::Null, ErrorCode::ParseError.into()) { match e { - SendError::Full => return Err(Error::Custom("Server buffer capacity exceeded".to_string())), - SendError::Disconnected => return Ok(()), + SendError::Full => break Err(Error::ConnectionBufferExceeded), + SendError::Disconnected => break Ok(()), } } } @@ -436,9 +437,7 @@ pub(crate) async fn background_task( method_executors.await; // Notify all listeners and close down associated tasks. - sink.close(); close_notify.notify_waiters(); - drop(conn); result @@ -446,20 +445,20 @@ pub(crate) async fn background_task( /// A task that waits for new messages via the `rx channel` and sends them out on the `WebSocket`. async fn send_task( - mut rx: mpsc::Receiver, + rx: mpsc::Receiver, mut ws_sender: Sender, mut stop_handle: StopHandle, ping_interval: Duration, ) { - // Received messages from the WebSocket. - let mut rx_item = rx.next(); - // Interval to send out continuously `pings`. let ping_interval = IntervalStream::new(tokio::time::interval(ping_interval)); let stopped = stop_handle.shutdown(); + let rx = ReceiverStream::new(rx); - tokio::pin!(ping_interval, stopped); + tokio::pin!(ping_interval, stopped, rx); + // Received messages from the WebSocket. + let mut rx_item = rx.next(); let next_ping = ping_interval.next(); let mut futs = future::select(next_ping, stopped); @@ -470,6 +469,7 @@ async fn send_task( // Received message. Either::Left((Some(response), not_ready)) => { // If websocket message send fail then terminate the connection. + tracing::info!("send message: {:?}", response); if let Err(err) = send_message(&mut ws_sender, response).await { tracing::error!("WS transport error: send failed: {}", err); break; From b3207661db90522baa81e3082189cb0bb121fa49 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 9 Jan 2023 14:27:43 +0100 Subject: [PATCH 06/60] fix nits --- core/Cargo.toml | 4 ++-- core/src/server/rpc_module.rs | 18 ++++++++++-------- server/Cargo.toml | 1 - server/src/server.rs | 1 + 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 24302a5057..30a68fe39e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -10,7 +10,6 @@ license = "MIT" anyhow = "1" async-trait = "0.1" beef = { version = "0.5.1", features = ["impl_serde"] } -futures-channel = "0.3.14" jsonrpsee-types = { path = "../types", version = "0.16.2" } thiserror = "1" serde = { version = "1.0", default-features = false, features = ["derive"] } @@ -20,6 +19,7 @@ tracing = "0.1.34" # optional deps arrayvec = { version = "0.7.1", optional = true } async-lock = { version = "2.4", optional = true } +futures-channel = { version = "0.3.14", optional = true } futures-util = { version = "0.3.14", default-features = false, optional = true } hyper = { version = "0.14.10", default-features = false, features = ["stream"], optional = true } rustc-hash = { version = "1", optional = true } @@ -45,7 +45,7 @@ server = [ "tokio/rt", "tokio/sync", ] -client = ["futures-util/sink", "futures-channel/sink", "futures-channel/std"] +client = ["futures-util/sink", "futures-channel/sink"] async-client = [ "async-lock", "client", diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 01e200cc82..bd510c2847 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -34,7 +34,6 @@ use crate::error::{Error, SubscriptionClosed}; use crate::id_providers::RandomIntegerIdProvider; use crate::server::helpers::MethodSink; use crate::traits::{IdProvider, ToRpcParams}; -use futures_channel::oneshot; use futures_util::future::Either; use futures_util::pin_mut; use futures_util::{future::BoxFuture, FutureExt, Stream, StreamExt, TryStream, TryStreamExt}; @@ -51,7 +50,7 @@ use mpsc::error::TrySendError; use parking_lot::Mutex; use rustc_hash::FxHashMap; use serde::{de::DeserializeOwned, Serialize}; -use tokio::sync::{mpsc, watch, Notify}; +use tokio::sync::{mpsc, oneshot, watch, Notify}; use super::helpers::MethodResponse; @@ -60,7 +59,7 @@ use super::helpers::MethodResponse; /// the `id`, `params`, a channel the function uses to communicate the result (or error) /// back to `jsonrpsee`, and the connection ID (useful for the websocket transport). pub type SyncMethod = Arc MethodResponse>; -/// Similar to [`SyncMethod`], but represents an asynchronous handler and takes an additional argument containing a [`ResourceGuard`] if configured. +/// Similar to [`SyncMethod`], but represents an asynchronous handler. pub type AsyncMethod<'a> = Arc, Params<'a>, ConnectionId, MaxResponseSize) -> BoxFuture<'a, MethodResponse>>; /// Method callback for subscriptions. @@ -76,19 +75,22 @@ pub type ConnectionId = usize; /// Max response size. pub type MaxResponseSize = usize; +/// A future the returns when the connection has been closed. +pub type CloseNotify = Arc; + /// Raw response from an RPC /// A 3-tuple containing: /// - Call result as a `String`, /// - a [`mpsc::Receiver`] to receive future subscription results -/// - a [`crate::server::helpers::SubscriptionPermit`] to allow subscribers to notify their [`SubscriptionSink`] when they disconnect. -pub type RawRpcResponse = (MethodResponse, mpsc::Receiver, Arc); +/// - a [`CloseNotify`] to allow subscribers to notify their [`SubscriptionSink`] when they disconnect. +pub type RawRpcResponse = (MethodResponse, mpsc::Receiver, CloseNotify); /// Helper struct to manage subscriptions. pub struct ConnState<'a> { /// Connection ID pub conn_id: ConnectionId, /// Get notified when the connection to subscribers is closed. - pub close_notify: Arc, + pub close_notify: CloseNotify, /// ID provider. pub id_provider: &'a dyn IdProvider, } @@ -764,7 +766,7 @@ pub struct SubscriptionSink { /// Sink. inner: MethodSink, /// Get notified when subscribers leave so we can exit - close_notify: Arc, + close_notify: CloseNotify, /// MethodCallback. method: &'static str, /// Shared Mutex of subscriptions for this method. @@ -1086,7 +1088,7 @@ impl Drop for SubscriptionSink { /// Wrapper struct that maintains a subscription "mainly" for testing. #[derive(Debug)] pub struct Subscription { - close_notify: Option>, + close_notify: Option, rx: mpsc::Receiver, sub_id: RpcSubscriptionId<'static>, } diff --git a/server/Cargo.toml b/server/Cargo.toml index 6b9a162dc5..bcfc6ff916 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -10,7 +10,6 @@ homepage = "https://github.com/paritytech/jsonrpsee" documentation = "https://docs.rs/jsonrpsee-server" [dependencies] -futures-channel = "0.3.14" futures-util = { version = "0.3.14", default-features = false, features = ["io", "async-await-macro"] } jsonrpsee-types = { path = "../types", version = "0.16.2" } jsonrpsee-core = { path = "../core", version = "0.16.2", features = ["server", "soketto", "http-helpers"] } diff --git a/server/src/server.rs b/server/src/server.rs index da61991c23..68f64de677 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -427,6 +427,7 @@ impl Builder { /// If this limit is exceeded the connection will be closed. pub fn set_buffer_size(mut self, c: u32) -> Self { self.settings.buffer_capacity = c; + self } /// Set maximum length for logging calls and responses. From f3a41d904af3671ed1d21731daeac6a63a0b0ee7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 9 Jan 2023 14:58:10 +0100 Subject: [PATCH 07/60] make futures_channel hard dependency --- core/Cargo.toml | 2 +- core/src/error.rs | 4 ++-- server/src/transport/ws.rs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 30a68fe39e..c1c95c20a3 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -10,6 +10,7 @@ license = "MIT" anyhow = "1" async-trait = "0.1" beef = { version = "0.5.1", features = ["impl_serde"] } +futures-channel = "0.3.14" jsonrpsee-types = { path = "../types", version = "0.16.2" } thiserror = "1" serde = { version = "1.0", default-features = false, features = ["derive"] } @@ -19,7 +20,6 @@ tracing = "0.1.34" # optional deps arrayvec = { version = "0.7.1", optional = true } async-lock = { version = "2.4", optional = true } -futures-channel = { version = "0.3.14", optional = true } futures-util = { version = "0.3.14", default-features = false, optional = true } hyper = { version = "0.14.10", default-features = false, features = ["stream"], optional = true } rustc-hash = { version = "1", optional = true } diff --git a/core/src/error.rs b/core/src/error.rs index e88253eb0e..48e74daa2e 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -121,8 +121,8 @@ pub enum Error { #[error("Empty batch request is not allowed")] EmptyBatchRequest, /// Max buffer capacity exceeded. - #[error("Max connection buffer capacity exceeded.")] - ConnectionBufferExceeded, + #[error("Max buffer capacity exceeded")] + MaxBufferExceeded, } impl Error { diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index c592d22910..6c9fe9b870 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -272,7 +272,7 @@ pub(crate) async fn background_task( // We close down the server is the connection buffer of messages has been exceeded. if buf_exceeded.load(Ordering::Relaxed) { - break Err(Error::ConnectionBufferExceeded); + break Err(Error::MaxBufferExceeded); } { @@ -308,7 +308,7 @@ pub(crate) async fn background_task( ); if let Err(e) = sink.send_error(Id::Null, reject_too_big_request(max_request_body_size)) { match e { - SendError::Full => break Err(Error::ConnectionBufferExceeded), + SendError::Full => break Err(Error::MaxBufferExceeded), SendError::Disconnected => break Ok(()), } } @@ -376,7 +376,7 @@ pub(crate) async fn background_task( logger.on_response(&response.result, request_start, TransportProtocol::WebSocket); if let Err(e) = sink.send_raw(response.result) { match e { - SendError::Full => break Err(Error::ConnectionBufferExceeded), + SendError::Full => break Err(Error::MaxBufferExceeded), SendError::Disconnected => break Ok(()), } } @@ -421,7 +421,7 @@ pub(crate) async fn background_task( _ => { if let Err(e) = sink.send_error(Id::Null, ErrorCode::ParseError.into()) { match e { - SendError::Full => break Err(Error::ConnectionBufferExceeded), + SendError::Full => break Err(Error::MaxBufferExceeded), SendError::Disconnected => break Ok(()), } } From 69ac4c427839bf339c437a8b2208deb54da830fa Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 9 Jan 2023 21:02:24 +0100 Subject: [PATCH 08/60] add real backpressure to rx --- benches/helpers.rs | 2 +- core/src/server/helpers.rs | 57 ++++++++++--- core/src/server/rpc_module.rs | 94 +++++++--------------- examples/examples/ws.rs | 10 ++- examples/examples/ws_pubsub_with_params.rs | 18 ++--- server/src/transport/ws.rs | 58 +++++-------- tests/tests/integration_tests.rs | 2 +- tests/tests/rpc_module.rs | 14 ++-- types/src/error.rs | 2 - 9 files changed, 122 insertions(+), 135 deletions(-) diff --git a/benches/helpers.rs b/benches/helpers.rs index 53ccef87f3..91a64e989d 100644 --- a/benches/helpers.rs +++ b/benches/helpers.rs @@ -148,7 +148,7 @@ pub async fn ws_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee::se module .register_subscription(SUB_METHOD_NAME, SUB_METHOD_NAME, UNSUB_METHOD_NAME, |_params, mut sink, _ctx| { let x = "Hello"; - tokio::spawn(async move { sink.send(&x) }); + tokio::spawn(async move { sink.send(&x).await }); Ok(()) }) .unwrap(); diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index 780cd75377..c0b096784e 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -26,13 +26,14 @@ use std::io; -use crate::server::rpc_module::SendError; use crate::tracing::tx_log_from_str; use crate::Error; use jsonrpsee_types::error::{ErrorCode, ErrorObject, ErrorResponse, OVERSIZED_RESPONSE_CODE, OVERSIZED_RESPONSE_MSG}; -use jsonrpsee_types::{Id, InvalidRequest, Response}; +use jsonrpsee_types::{ErrorObjectOwned, Id, InvalidRequest, Response}; use serde::Serialize; -use tokio::sync::mpsc; +use tokio::sync::mpsc::{self, Permit}; + +use super::rpc_module::DisconnectError; /// Bounded writer that allows writing at most `max_len` bytes. /// @@ -107,28 +108,64 @@ impl MethodSink { } /// Send a JSON-RPC error to the client - pub fn send_error(&mut self, id: Id, error: ErrorObject) -> Result<(), SendError> { + pub async fn send_error(&self, id: Id<'static>, error: ErrorObjectOwned) -> Result<(), DisconnectError> { let json = serde_json::to_string(&ErrorResponse::borrowed(error, id)).expect("valid JSON; qed"); - self.send_raw(json) + self.send_raw(json).await } /// Helper for sending the general purpose `Error` as a JSON-RPC errors to the client. - pub fn send_call_error(&mut self, id: Id, err: Error) -> Result<(), SendError> { - self.send_error(id, err.into()) + pub async fn send_call_error(&self, id: Id<'static>, err: Error) -> Result<(), DisconnectError> { + self.send_error(id, err.into()).await } /// Send a raw JSON-RPC message to the client, `MethodSink` does not check verify the validity /// of the JSON being sent. - pub fn send_raw(&mut self, json: String) -> Result<(), SendError> { + pub async fn send_raw(&self, json: String) -> Result<(), DisconnectError> { tx_log_from_str(&json, self.max_log_length); - self.tx.try_send(json).map_err(Into::into) + self.tx.send(json).await } - /// Get the maximum number of permitted subscriptions. + /// Get the max response size. pub const fn max_response_size(&self) -> u32 { self.max_response_size } + + /// Waits for channel capacity. Once capacity to send one message is available, it is reserved for the caller. + pub async fn reserve(&self) -> Result> { + match self.tx.reserve().await { + Ok(permit) => Ok(MethodSinkPermit { tx: permit, max_log_length: self.max_log_length }), + Err(e) => Err(e), + } + } +} + +/// A method sink with reserved spot in the bounded queue. +#[derive(Debug)] +pub struct MethodSinkPermit<'a> { + tx: Permit<'a, String>, + max_log_length: u32, +} + +impl<'a> MethodSinkPermit<'a> { + /// Send a JSON-RPC error to the client + pub fn send_error(self, id: Id, error: ErrorObject) { + let json = serde_json::to_string(&ErrorResponse::borrowed(error, id)).expect("valid JSON; qed"); + + self.send_raw(json) + } + + /// Helper for sending the general purpose `Error` as a JSON-RPC errors to the client. + pub fn send_call_error(self, id: Id, err: Error) { + self.send_error(id, err.into()) + } + + /// Send a raw JSON-RPC message to the client, `MethodSink` does not check verify the validity + /// of the JSON being sent. + pub fn send_raw(self, json: String) { + tx_log_from_str(&json, self.max_log_length); + self.tx.send(json) + } } /// Figure out if this is a sufficiently complete request that we can extract an [`Id`] out of, or just plain diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index bd510c2847..6f3d979535 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -46,7 +46,6 @@ use jsonrpsee_types::{ ErrorResponse, Id, Params, Request, Response, SubscriptionId as RpcSubscriptionId, SubscriptionPayload, SubscriptionResponse, SubscriptionResult, }; -use mpsc::error::TrySendError; use parking_lot::Mutex; use rustc_hash::FxHashMap; use serde::{de::DeserializeOwned, Serialize}; @@ -85,6 +84,9 @@ pub type CloseNotify = Arc; /// - a [`CloseNotify`] to allow subscribers to notify their [`SubscriptionSink`] when they disconnect. pub type RawRpcResponse = (MethodResponse, mpsc::Receiver, CloseNotify); +/// Disconnect error. +pub type DisconnectError = mpsc::error::SendError; + /// Helper struct to manage subscriptions. pub struct ConnState<'a> { /// Connection ID @@ -111,9 +113,9 @@ pub enum SubscriptionSinkError { /// Something failed during the init of the subscription. #[error("{0:?}")] Subscribe(SubscriptionAcceptRejectError), - #[error("{0}")] - /// Something failed when sending a message via the subscription. - Send(#[from] SendError), + /// The connection is closed. + #[error("The connection is closed")] + Disconnected, #[error("{0}")] /// Something when trying to decode the message. Serialize(#[from] serde_json::Error), @@ -731,35 +733,6 @@ impl RpcModule { /// Returns once the unsubscribe method has been called. type UnsubscribeCall = Option>; -/// Represents a send error over a bounded channel. -#[derive(Debug, Copy, Clone, thiserror::Error)] -pub enum SendError { - /// The channel was full. - #[error("The channel is full")] - Full, - /// The channel was disconnected. - #[error("The channel is disconnected")] - Disconnected, -} - -impl From> for SendError { - fn from(err: TrySendError) -> Self { - match err { - TrySendError::Closed(_) => Self::Disconnected, - TrySendError::Full(_) => Self::Full, - } - } -} - -impl From for SubscriptionAcceptRejectError { - fn from(err: SendError) -> Self { - match err { - SendError::Disconnected => Self::RemotePeerAborted, - SendError::Full => Self::Full, - } - } -} - /// Represents a single subscription. #[derive(Debug)] pub struct SubscriptionSink { @@ -784,25 +757,25 @@ pub struct SubscriptionSink { impl SubscriptionSink { /// Reject the subscription call from [`ErrorObject`]. - pub fn reject(&mut self, err: impl Into) -> Result<(), SubscriptionAcceptRejectError> { + pub async fn reject(&mut self, err: impl Into) -> Result<(), SubscriptionAcceptRejectError> { let (id, subscribe_call) = self.id.take().ok_or(SubscriptionAcceptRejectError::AlreadyCalled)?; let err = MethodResponse::error(id, err.into()); - self.answer_subscription(err, subscribe_call).unwrap(); + self.answer_subscription(err, subscribe_call).await.unwrap(); Ok(()) } /// Attempt to accept the subscription and respond the subscription method call. /// /// Fails if the connection was closed, or if called multiple times. - pub fn accept(&mut self) -> Result<(), SubscriptionAcceptRejectError> { + pub async fn accept(&mut self) -> Result<(), SubscriptionAcceptRejectError> { let (id, subscribe_call) = self.id.take().ok_or(SubscriptionAcceptRejectError::AlreadyCalled)?; let response = MethodResponse::response(id, &self.uniq_sub.sub_id, self.inner.max_response_size() as usize); let success = response.success; - self.answer_subscription(response, subscribe_call).unwrap(); + self.answer_subscription(response, subscribe_call).await?; if success { let (tx, rx) = watch::channel(()); @@ -834,19 +807,14 @@ impl SubscriptionSink { /// - `Ok(false)` if the sink was closed (either because the subscription was closed or the connection was terminated), /// or the subscription could not be accepted. /// - `Err(err)` if the message could not be serialized. - pub fn send(&mut self, result: &T) -> Result<(), SubscriptionSinkError> { - match self.accept() { + pub async fn send(&mut self, result: &T) -> Result<(), SubscriptionSinkError> { + match self.accept().await { Ok(_) => (), Err(SubscriptionAcceptRejectError::AlreadyCalled) => (), - Err(SubscriptionAcceptRejectError::Full) => { - return Err(SubscriptionSinkError::Send(SendError::Full)); - } - Err(SubscriptionAcceptRejectError::RemotePeerAborted) => { - return Err(SubscriptionSinkError::Send(SendError::Disconnected)); - } + Err(SubscriptionAcceptRejectError::RemotePeerAborted) => return Err(SubscriptionSinkError::Disconnected), }; - self.send_without_accept(result) + self.send_without_accept(result).await } /// Reads data from the `stream` and sends back data on the subscription @@ -897,7 +865,7 @@ impl SubscriptionSink { T: Serialize, E: std::fmt::Display, { - if let Err(SubscriptionAcceptRejectError::RemotePeerAborted) = self.accept() { + if let Err(SubscriptionAcceptRejectError::RemotePeerAborted) = self.accept().await { return SubscriptionClosed::RemotePeerAborted; } @@ -927,14 +895,11 @@ impl SubscriptionSink { match futures_util::future::select(stream_item, closed_fut).await { // The app sent us a value to send back to the subscribers Either::Left((Ok(Some(result)), next_closed_fut)) => { - match self.send_without_accept(&result) { + match self.send_without_accept(&result).await { Ok(()) => (), - Err(SubscriptionSinkError::Send(SendError::Disconnected)) => { + Err(SubscriptionSinkError::Disconnected) => { break SubscriptionClosed::RemotePeerAborted; } - Err(SubscriptionSinkError::Send(SendError::Full)) => { - break SubscriptionClosed::Full; - } Err(err) => { let err = ErrorObject::owned(SUBSCRIPTION_CLOSED_WITH_ERROR, err.to_string(), None::<()>); break SubscriptionClosed::Failed(err); @@ -993,14 +958,14 @@ impl SubscriptionSink { /// This is similar to the [`SubscriptionSink::send`], but it does not try to accept /// the subscription prior to sending. #[inline] - fn send_without_accept(&mut self, result: &T) -> Result<(), SubscriptionSinkError> { + async fn send_without_accept(&mut self, result: &T) -> Result<(), SubscriptionSinkError> { // Only possible to trigger when the connection is dropped. if self.is_closed() { - return Err(SubscriptionSinkError::Send(SendError::Disconnected)); + return Err(SubscriptionSinkError::Disconnected); } let msg = self.build_message(result)?; - self.inner.send_raw(msg).map_err(SubscriptionSinkError::Send) + self.inner.send_raw(msg).await.map_err(|_| SubscriptionSinkError::Disconnected) } fn is_active_subscription(&self) -> bool { @@ -1010,15 +975,16 @@ impl SubscriptionSink { } } - fn answer_subscription( + async fn answer_subscription( &mut self, response: MethodResponse, subscribe_call: oneshot::Sender, - ) -> Result<(), SendError> { - self.inner.send_raw(response.result.clone())?; - subscribe_call.send(response).map_err(|_| SendError::Disconnected)?; - - Ok(()) + ) -> Result<(), SubscriptionAcceptRejectError> { + self.inner + .send_raw(response.result.clone()) + .await + .map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; + subscribe_call.send(response).map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted) } fn build_message(&self, result: &T) -> Result { @@ -1057,13 +1023,13 @@ impl SubscriptionSink { /// } /// ``` /// - pub fn close(self, err: impl Into) -> bool { + pub async fn close(self, err: impl Into) -> bool { if self.is_active_subscription() { - if let Some((mut sink, _)) = self.subscribers.lock().remove(&self.uniq_sub) { + if let Some((sink, _)) = self.subscribers.lock().remove(&self.uniq_sub) { tracing::debug!("Closing subscription: {:?}", self.uniq_sub.sub_id); let msg = self.build_error_message(&err.into()).expect("valid json infallible; qed"); - return sink.send_raw(msg).is_ok(); + return sink.send_raw(msg).await.is_ok(); } } false diff --git a/examples/examples/ws.rs b/examples/examples/ws.rs index 15a2c8a0cb..5e77b22e45 100644 --- a/examples/examples/ws.rs +++ b/examples/examples/ws.rs @@ -40,19 +40,21 @@ async fn main() -> anyhow::Result<()> { tracing_subscriber::FmtSubscriber::builder().with_env_filter(filter).finish().try_init()?; let addr = run_server().await?; - let url = format!("ws://{}", addr); + /*let url = format!("ws://{}", addr); let client = WsClientBuilder::default().build(&url).await?; let response: String = client.request("say_hello", rpc_params![]).await?; - tracing::info!("response: {:?}", response); + tracing::info!("response: {:?}", response);*/ + + futures::future::pending::<()>().await; Ok(()) } async fn run_server() -> anyhow::Result { - let server = ServerBuilder::default().build("127.0.0.1:0").await?; + let server = ServerBuilder::default().set_buffer_size(2).build("127.0.0.1:9944").await?; let mut module = RpcModule::new(()); - module.register_method("say_hello", |_, _| Ok("lo"))?; + module.register_method("say_hello", |_, _| Ok("a".repeat(1024)))?; let addr = server.local_addr()?; let handle = server.start(module)?; diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index 1e56352ba4..6302ec6a56 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -44,7 +44,7 @@ async fn main() -> anyhow::Result<()> { .expect("setting default subscriber failed"); let addr = run_server().await?; - let url = format!("ws://{}", addr); + /*let url = format!("ws://{}", addr); let client = WsClientBuilder::default().build(&url).await?; @@ -56,14 +56,16 @@ async fn main() -> anyhow::Result<()> { // Subscription with multiple parameters let mut sub_params_two: Subscription = client.subscribe("sub_params_two", rpc_params![2, 5], "unsub_params_two").await?; - tracing::info!("subscription with two params: {:?}", sub_params_two.next().await); + tracing::info!("subscription with two params: {:?}", sub_params_two.next().await);*/ + + futures::future::pending::<()>().await; Ok(()) } async fn run_server() -> anyhow::Result { const LETTERS: &str = "abcdefghijklmnopqrstuvxyz"; - let server = ServerBuilder::default().build("127.0.0.1:0").await?; + let server = ServerBuilder::default().build("127.0.0.1:9944").await?; let mut module = RpcModule::new(()); module .register_subscription("sub_one_param", "sub_one_param", "unsub_one_param", |params, mut sink, _| { @@ -74,9 +76,8 @@ async fn run_server() -> anyhow::Result { let stream = IntervalStream::new(interval).map(move |_| item); tokio::spawn(async move { - if let SubscriptionClosed::Failed(err) = sink.pipe_from_stream(stream).await { - sink.close(err); - } + let res = sink.pipe_from_stream(stream).await; + tracing::info!("subscription result: {:?}", res); }); Ok(()) }) @@ -91,9 +92,8 @@ async fn run_server() -> anyhow::Result { let stream = IntervalStream::new(interval).map(move |_| item); tokio::spawn(async move { - if let SubscriptionClosed::Failed(err) = sink.pipe_from_stream(stream).await { - sink.close(err); - } + let res = sink.pipe_from_stream(stream).await; + tracing::info!("subscription result: {:?}", res); }); Ok(()) diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 6c9fe9b870..667886302a 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -1,5 +1,4 @@ use std::pin::Pin; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; @@ -14,7 +13,7 @@ use futures_util::stream::FuturesOrdered; use futures_util::{Future, FutureExt, StreamExt}; use hyper::upgrade::Upgraded; use jsonrpsee_core::server::helpers::{prepare_error, BatchResponse, BatchResponseBuilder, MethodResponse, MethodSink}; -use jsonrpsee_core::server::rpc_module::{ConnState, MethodKind, Methods, SendError}; +use jsonrpsee_core::server::rpc_module::{ConnState, MethodKind, Methods}; use jsonrpsee_core::tracing::{rx_log_from_json, tx_log_from_str}; use jsonrpsee_core::traits::IdProvider; use jsonrpsee_core::{Error, JsonRawValue}; @@ -255,9 +254,8 @@ pub(crate) async fn background_task( } = svc; let (tx, rx) = mpsc::channel::(buffer_capacity as usize); - let mut sink = MethodSink::new_with_limit(tx, max_response_body_size, max_log_length); + let sink = MethodSink::new_with_limit(tx, max_response_body_size, max_log_length); let close_notify = Arc::new(Notify::new()); - let buf_exceeded = Arc::new(AtomicBool::new(false)); // Spawn another task that sends out the responses on the Websocket. tokio::spawn(send_task(rx, sender, stop_handle.clone(), ping_interval)); @@ -270,10 +268,16 @@ pub(crate) async fn background_task( let result = loop { data.clear(); - // We close down the server is the connection buffer of messages has been exceeded. - if buf_exceeded.load(Ordering::Relaxed) { - break Err(Error::MaxBufferExceeded); - } + // Wait until the is space in the bounded channel and + // don't poll the underlying socket until a spot has been reserved. + // + // This will force the client to read socket on the other side + // otherwise the socket will not be read again. + let sink_permit = match sink.reserve().await { + Ok(p) => p, + // reserve only fails if the channel is disconnected. + Err(_) => break Ok(()), + }; { // Need the extra scope to drop this pinned future and reclaim access to `data` @@ -306,12 +310,8 @@ pub(crate) async fn background_task( current, maximum ); - if let Err(e) = sink.send_error(Id::Null, reject_too_big_request(max_request_body_size)) { - match e { - SendError::Full => break Err(Error::MaxBufferExceeded), - SendError::Disconnected => break Ok(()), - } - } + sink_permit.send_error(Id::Null, reject_too_big_request(max_request_body_size)); + continue; } @@ -333,11 +333,10 @@ pub(crate) async fn background_task( match first_non_whitespace { Some(b'{') => { let data = std::mem::take(&mut data); - let mut sink = sink.clone(); + let sink = sink.clone(); let methods = &methods; let id_provider = &*id_provider; let close_notify = close_notify.clone(); - let buf_exceeded = buf_exceeded.clone(); let fut = async move { let call = CallData { @@ -358,9 +357,7 @@ pub(crate) async fn background_task( } MethodResult::SendAndLogger(r) => { logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); - if let Err(SendError::Full) = sink.send_raw(r.result) { - buf_exceeded.store(true, Ordering::Relaxed); - } + sink_permit.send_raw(r.result); } }; } @@ -374,21 +371,15 @@ pub(crate) async fn background_task( ErrorObject::borrowed(BATCHES_NOT_SUPPORTED_CODE, &BATCHES_NOT_SUPPORTED_MSG, None), ); logger.on_response(&response.result, request_start, TransportProtocol::WebSocket); - if let Err(e) = sink.send_raw(response.result) { - match e { - SendError::Full => break Err(Error::MaxBufferExceeded), - SendError::Disconnected => break Ok(()), - } - } + sink_permit.send_raw(response.result); } Some(b'[') => { // Make sure the following variables are not moved into async closure below. let methods = &methods; - let mut sink = sink.clone(); + let sink = sink.clone(); let id_provider = id_provider.clone(); let data = std::mem::take(&mut data); let close_notify = close_notify.clone(); - let buf_exceeded = buf_exceeded.clone(); let fut = async move { let response = process_batch_request(Batch { @@ -410,21 +401,14 @@ pub(crate) async fn background_task( if let Some(response) = response { tx_log_from_str(&response.result, max_log_length); logger.on_response(&response.result, request_start, TransportProtocol::WebSocket); - if let Err(SendError::Full) = sink.send_raw(response.result) { - buf_exceeded.store(true, Ordering::Relaxed); - } + sink_permit.send_raw(response.result); } }; method_executors.add(Box::pin(fut)); } _ => { - if let Err(e) = sink.send_error(Id::Null, ErrorCode::ParseError.into()) { - match e { - SendError::Full => break Err(Error::MaxBufferExceeded), - SendError::Disconnected => break Ok(()), - } - } + sink_permit.send_error(Id::Null, ErrorCode::ParseError.into()); } } }; @@ -469,11 +453,11 @@ async fn send_task( // Received message. Either::Left((Some(response), not_ready)) => { // If websocket message send fail then terminate the connection. - tracing::info!("send message: {:?}", response); if let Err(err) = send_message(&mut ws_sender, response).await { tracing::error!("WS transport error: send failed: {}", err); break; } + rx_item = rx.next(); futs = not_ready; } diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 78378c7b0f..1ba7df553c 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -437,7 +437,7 @@ async fn ws_server_should_stop_subscription_after_client_drop() { sink.accept().unwrap(); tokio::spawn(async move { let close_err = loop { - match sink.send(&1_usize) { + match sink.send(&1_usize).await { Ok(_) => (), Err(SubscriptionSinkError::Send(SendError::Disconnected)) => { break ErrorObject::borrowed(0, &"Subscription terminated successfully", None) diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index a4f18c4274..efe32c2b1a 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -235,12 +235,12 @@ async fn subscribing_without_server() { module .register_subscription("my_sub", "my_sub", "my_unsub", |_, mut sink, _| { let mut stream_data = vec!['0', '1', '2']; - sink.accept()?; tokio::spawn(async move { + sink.accept().await.unwrap(); while let Some(letter) = stream_data.pop() { tracing::debug!("This is your friendly subscription sending data."); - let _ = sink.send(&letter); + let _ = sink.send(&letter).await.unwrap(); tokio::time::sleep(std::time::Duration::from_millis(500)).await; } let close = ErrorObject::borrowed(0, &"closed successfully", None); @@ -267,19 +267,19 @@ async fn close_test_subscribing_without_server() { let mut module = RpcModule::new(()); module .register_subscription("my_sub", "my_sub", "my_unsub", |_, mut sink, _| { - sink.accept()?; - tokio::spawn(async move { + sink.accept().await.unwrap(); + // make sure to only send one item - sink.send(&"lo").unwrap(); + sink.send(&"lo").await.unwrap(); while !sink.is_closed() { tracing::debug!("[test] Sink is open, sleeping"); tokio::time::sleep(std::time::Duration::from_millis(500)).await; } - match sink.send(&"lo") { + match sink.send(&"lo").await { Ok(_) => panic!("The sink should be closed"), - Err(SubscriptionSinkError::Send(SendError::Disconnected)) => { + Err(SubscriptionSinkError::Disconnected) => { sink.close(SubscriptionClosed::RemotePeerAborted); } Err(other) => panic!("Unexpected error: {:?}", other), diff --git a/types/src/error.rs b/types/src/error.rs index ed12a6840c..9644faefb3 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -122,8 +122,6 @@ pub enum SubscriptionAcceptRejectError { AlreadyCalled, /// The remote peer closed the connection or called the unsubscribe method. RemotePeerAborted, - /// The server capacity was exceeded. - Full, } /// Owned variant of [`ErrorObject`]. From d11ec7ed2e7b7ef8d2086bdaa796bddc88806b23 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 10 Jan 2023 17:22:38 +0100 Subject: [PATCH 09/60] PoC with crossbeam queue --- core/Cargo.toml | 2 + core/src/server/helpers.rs | 23 +-------- core/src/server/rpc_module.rs | 57 +++++++++++++++------- examples/examples/ws_pubsub_broadcast.rs | 4 +- examples/examples/ws_pubsub_with_params.rs | 2 +- server/src/transport/ws.rs | 8 ++- tests/tests/integration_tests.rs | 2 +- 7 files changed, 53 insertions(+), 45 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index c1c95c20a3..a4ea27692c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -31,6 +31,7 @@ wasm-bindgen-futures = { version = "0.4.19", optional = true } futures-timer = { version = "3", optional = true } globset = { version = "0.4", optional = true } http = { version = "0.2.7", optional = true } +crossbeam-queue = { version = "0.3", optional = true } [features] default = [] @@ -44,6 +45,7 @@ server = [ "rand", "tokio/rt", "tokio/sync", + "crossbeam-queue", ] client = ["futures-util/sink", "futures-channel/sink"] async-client = [ diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index c0b096784e..b1b1bbf6c6 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -29,7 +29,7 @@ use std::io; use crate::tracing::tx_log_from_str; use crate::Error; use jsonrpsee_types::error::{ErrorCode, ErrorObject, ErrorResponse, OVERSIZED_RESPONSE_CODE, OVERSIZED_RESPONSE_MSG}; -use jsonrpsee_types::{ErrorObjectOwned, Id, InvalidRequest, Response}; +use jsonrpsee_types::{Id, InvalidRequest, Response}; use serde::Serialize; use tokio::sync::mpsc::{self, Permit}; @@ -107,25 +107,6 @@ impl MethodSink { self.tx.is_closed() } - /// Send a JSON-RPC error to the client - pub async fn send_error(&self, id: Id<'static>, error: ErrorObjectOwned) -> Result<(), DisconnectError> { - let json = serde_json::to_string(&ErrorResponse::borrowed(error, id)).expect("valid JSON; qed"); - - self.send_raw(json).await - } - - /// Helper for sending the general purpose `Error` as a JSON-RPC errors to the client. - pub async fn send_call_error(&self, id: Id<'static>, err: Error) -> Result<(), DisconnectError> { - self.send_error(id, err.into()).await - } - - /// Send a raw JSON-RPC message to the client, `MethodSink` does not check verify the validity - /// of the JSON being sent. - pub async fn send_raw(&self, json: String) -> Result<(), DisconnectError> { - tx_log_from_str(&json, self.max_log_length); - self.tx.send(json).await - } - /// Get the max response size. pub const fn max_response_size(&self) -> u32 { self.max_response_size @@ -163,8 +144,8 @@ impl<'a> MethodSinkPermit<'a> { /// Send a raw JSON-RPC message to the client, `MethodSink` does not check verify the validity /// of the JSON being sent. pub fn send_raw(self, json: String) { + self.tx.send(json.clone()); tx_log_from_str(&json, self.max_log_length); - self.tx.send(json) } } diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 6f3d979535..b1936c2b37 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -683,8 +683,11 @@ impl RpcModule { // response to the subscription call. let (tx, rx) = oneshot::channel(); + let queue = Arc::new(crossbeam_queue::ArrayQueue::::new(4)); + let sink = SubscriptionSink { - inner: method_sink, + inner: method_sink.clone(), + queue: queue.clone(), close_notify: conn.close_notify, method: notif_method_name, subscribers: subscribers.clone(), @@ -693,6 +696,14 @@ impl RpcModule { unsubscribe: None, }; + tokio::spawn(async move { + while let Ok(permit) = method_sink.reserve().await { + if let Some(val) = queue.pop() { + permit.send_raw(val); + } + } + }); + // The callback returns a `SubscriptionResult` for better ergonomics and is not propagated further. if callback(params, sink, ctx.clone()).is_err() { tracing::warn!("Subscribe call `{}` failed", subscribe_method_name); @@ -738,6 +749,8 @@ type UnsubscribeCall = Option>; pub struct SubscriptionSink { /// Sink. inner: MethodSink, + /// Queue of messages that are waiting to be sent. + queue: Arc>, /// Get notified when subscribers leave so we can exit close_notify: CloseNotify, /// MethodCallback. @@ -762,7 +775,7 @@ impl SubscriptionSink { let err = MethodResponse::error(id, err.into()); - self.answer_subscription(err, subscribe_call).await.unwrap(); + self.answer_subscription(err, subscribe_call).await?; Ok(()) } @@ -803,10 +816,8 @@ impl SubscriptionSink { /// Send a message back to subscribers. /// /// Returns - /// - `Ok(true)` if the message could be send. - /// - `Ok(false)` if the sink was closed (either because the subscription was closed or the connection was terminated), - /// or the subscription could not be accepted. - /// - `Err(err)` if the message could not be serialized. + /// - `Ok(())` if the message could be sent. + /// - `Err(err)` if an error occurred when trying to send the message. pub async fn send(&mut self, result: &T) -> Result<(), SubscriptionSinkError> { match self.accept().await { Ok(_) => (), @@ -814,7 +825,7 @@ impl SubscriptionSink { Err(SubscriptionAcceptRejectError::RemotePeerAborted) => return Err(SubscriptionSinkError::Disconnected), }; - self.send_without_accept(result).await + self.send_without_accept(result) } /// Reads data from the `stream` and sends back data on the subscription @@ -895,7 +906,7 @@ impl SubscriptionSink { match futures_util::future::select(stream_item, closed_fut).await { // The app sent us a value to send back to the subscribers Either::Left((Ok(Some(result)), next_closed_fut)) => { - match self.send_without_accept(&result).await { + match self.send_without_accept(&result) { Ok(()) => (), Err(SubscriptionSinkError::Disconnected) => { break SubscriptionClosed::RemotePeerAborted; @@ -958,14 +969,18 @@ impl SubscriptionSink { /// This is similar to the [`SubscriptionSink::send`], but it does not try to accept /// the subscription prior to sending. #[inline] - async fn send_without_accept(&mut self, result: &T) -> Result<(), SubscriptionSinkError> { + fn send_without_accept(&mut self, result: &T) -> Result<(), SubscriptionSinkError> { // Only possible to trigger when the connection is dropped. if self.is_closed() { return Err(SubscriptionSinkError::Disconnected); } let msg = self.build_message(result)?; - self.inner.send_raw(msg).await.map_err(|_| SubscriptionSinkError::Disconnected) + if let Some(rm) = self.queue.force_push(msg) { + tracing::debug!("Client can't keep up with subscription notifs; replaced old message: {:?}", rm); + } + + Ok(()) } fn is_active_subscription(&self) -> bool { @@ -980,10 +995,8 @@ impl SubscriptionSink { response: MethodResponse, subscribe_call: oneshot::Sender, ) -> Result<(), SubscriptionAcceptRejectError> { - self.inner - .send_raw(response.result.clone()) - .await - .map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; + let permit = self.inner.reserve().await.map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; + permit.send_raw(response.result.clone()); subscribe_call.send(response).map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted) } @@ -1023,16 +1036,24 @@ impl SubscriptionSink { /// } /// ``` /// - pub async fn close(self, err: impl Into) -> bool { + pub fn close(self, err: impl Into) -> impl Future { if self.is_active_subscription() { if let Some((sink, _)) = self.subscribers.lock().remove(&self.uniq_sub) { tracing::debug!("Closing subscription: {:?}", self.uniq_sub.sub_id); let msg = self.build_error_message(&err.into()).expect("valid json infallible; qed"); - return sink.send_raw(msg).await.is_ok(); + + return Either::Right(async move { + let permit = match sink.reserve().await { + Ok(permit) => permit, + Err(_) => return false, + }; + permit.send_raw(msg); + true + }); } } - false + Either::Left(futures_util::future::ready(false)) } } @@ -1044,7 +1065,7 @@ impl Drop for SubscriptionSink { // because that's how the previous PendingSubscription logic // worked. let err = MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidParams)); - let _ = self.answer_subscription(err, subscribe_call); + let _ = self.answer_subscription(err, subscribe_call).now_or_never(); } else if self.is_active_subscription() { self.subscribers.lock().remove(&self.uniq_sub); } diff --git a/examples/examples/ws_pubsub_broadcast.rs b/examples/examples/ws_pubsub_broadcast.rs index 116d445e00..6305fc8870 100644 --- a/examples/examples/ws_pubsub_broadcast.rs +++ b/examples/examples/ws_pubsub_broadcast.rs @@ -77,12 +77,12 @@ async fn run_server() -> anyhow::Result { tokio::spawn(async move { match sink.pipe_from_try_stream(rx).await { SubscriptionClosed::Success => { - sink.close(SubscriptionClosed::Success); + sink.close(SubscriptionClosed::Success).await; } SubscriptionClosed::RemotePeerAborted => (), SubscriptionClosed::Full => (), SubscriptionClosed::Failed(err) => { - sink.close(err); + sink.close(err).await; } }; }); diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index 6302ec6a56..b2aea136d1 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -65,7 +65,7 @@ async fn main() -> anyhow::Result<()> { async fn run_server() -> anyhow::Result { const LETTERS: &str = "abcdefghijklmnopqrstuvxyz"; - let server = ServerBuilder::default().build("127.0.0.1:9944").await?; + let server = ServerBuilder::default().set_buffer_size(10).build("127.0.0.1:9944").await?; let mut module = RpcModule::new(()); module .register_subscription("sub_one_param", "sub_one_param", "unsub_one_param", |params, mut sink, _| { diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 667886302a..5b8d7d7a1a 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -226,7 +226,7 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData let r = response.as_inner(); - tx_log_from_str(&r.result, max_log_length); + //tx_log_from_str(&r.result, max_log_length); logger.on_result(name, r.success, request_start, TransportProtocol::WebSocket); response } @@ -434,6 +434,7 @@ async fn send_task( mut stop_handle: StopHandle, ping_interval: Duration, ) { + /* // Interval to send out continuously `pings`. let ping_interval = IntervalStream::new(tokio::time::interval(ping_interval)); let stopped = stop_handle.shutdown(); @@ -482,7 +483,10 @@ async fn send_task( break; } } - } + }*/ + + // fake that no messages were read. + future::pending::<()>().await; // Terminate connection and send close message. let _ = ws_sender.close().await; diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 1ba7df553c..4198bd4f83 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -40,7 +40,7 @@ use helpers::{ use hyper::http::HeaderValue; use jsonrpsee::core::client::{ClientT, IdKind, Subscription, SubscriptionClientT}; use jsonrpsee::core::params::{ArrayParams, BatchRequestBuilder}; -use jsonrpsee::core::server::rpc_module::{SendError, SubscriptionSinkError}; +use jsonrpsee::core::server::rpc_module::{DisconnectError, SubscriptionSinkError}; use jsonrpsee::core::{Error, JsonValue}; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::rpc_params; From 6ec941d0d1afd79aebaa4cb7bb5ca88f5e8358fd Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 13 Jan 2023 18:06:45 +0100 Subject: [PATCH 10/60] remove pipe_from_stream --- core/src/error.rs | 41 +-- core/src/server/helpers.rs | 19 +- core/src/server/rpc_module.rs | 300 ++++++--------------- examples/examples/ws_pubsub_broadcast.rs | 1 - examples/examples/ws_pubsub_with_params.rs | 25 +- server/src/transport/ws.rs | 49 ++-- tests/tests/integration_tests.rs | 2 +- 7 files changed, 143 insertions(+), 294 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index 48e74daa2e..9c32156ca5 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -27,8 +27,7 @@ use std::fmt; use jsonrpsee_types::error::{ - CallError, ErrorObject, ErrorObjectOwned, CALL_EXECUTION_FAILED_CODE, INVALID_PARAMS_CODE, SUBSCRIPTION_CLOSED, - UNKNOWN_ERROR_CODE, + CallError, ErrorObject, ErrorObjectOwned, CALL_EXECUTION_FAILED_CODE, INVALID_PARAMS_CODE, UNKNOWN_ERROR_CODE, }; /// Convenience type for displaying errors. @@ -120,9 +119,6 @@ pub enum Error { /// Empty batch request. #[error("Empty batch request is not allowed")] EmptyBatchRequest, - /// Max buffer capacity exceeded. - #[error("Max buffer capacity exceeded")] - MaxBufferExceeded, } impl Error { @@ -152,41 +148,6 @@ impl From for ErrorObjectOwned { } } -/// A type to represent when a subscription gets closed -/// by either the server or client side. -#[derive(Clone, Debug)] -pub enum SubscriptionClosed { - /// The remote peer closed the connection or called the unsubscribe method. - RemotePeerAborted, - /// The subscription was completed successfully by the server. - Success, - /// The subscription was dropped because the message capacity buffer was exceeded. - Full, - /// The subscription failed during execution by the server. - Failed(ErrorObject<'static>), -} - -impl From for ErrorObjectOwned { - fn from(err: SubscriptionClosed) -> Self { - match err { - SubscriptionClosed::RemotePeerAborted => { - ErrorObject::owned(SUBSCRIPTION_CLOSED, "Subscription was closed by the remote peer", None::<()>) - } - SubscriptionClosed::Full => ErrorObject::owned( - SUBSCRIPTION_CLOSED, - "Subscription was closed because the bounded buffer capacity was exceeded", - None::<()>, - ), - SubscriptionClosed::Success => ErrorObject::owned( - SUBSCRIPTION_CLOSED, - "Subscription was completed by the server successfully", - None::<()>, - ), - SubscriptionClosed::Failed(err) => err, - } - } -} - /// Generic transport error. #[derive(Debug, thiserror::Error)] pub enum GenericTransportError { diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index b1b1bbf6c6..b352adb9de 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -33,7 +33,7 @@ use jsonrpsee_types::{Id, InvalidRequest, Response}; use serde::Serialize; use tokio::sync::mpsc::{self, Permit}; -use super::rpc_module::DisconnectError; +use super::rpc_module::{DisconnectError, TrySendError}; /// Bounded writer that allows writing at most `max_len` bytes. /// @@ -107,11 +107,28 @@ impl MethodSink { self.tx.is_closed() } + /// Same as [`tokio::sync::mpsc::Sender::closed`]. + pub async fn closed(&self) { + self.tx.closed().await + } + /// Get the max response size. pub const fn max_response_size(&self) -> u32 { self.max_response_size } + /// Non-blocking send which fails if the channel is closed or full + /// + /// Returns the message if the send fails such that either can be thrown away or re-sent later. + pub fn try_send(&mut self, msg: String) -> Result<(), TrySendError> { + self.tx.try_send(msg) + } + + /// Async send which will wait until there is space in channel buffer or that the subscription is disconnected. + pub async fn send(&self, msg: String) -> Result<(), DisconnectError> { + self.tx.send(msg).await + } + /// Waits for channel capacity. Once capacity to send one message is available, it is reserved for the caller. pub async fn reserve(&self) -> Result> { match self.tx.reserve().await { diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index b1936c2b37..503526115f 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -30,17 +30,12 @@ use std::future::Future; use std::ops::{Deref, DerefMut}; use std::sync::Arc; -use crate::error::{Error, SubscriptionClosed}; +use crate::error::Error; use crate::id_providers::RandomIntegerIdProvider; use crate::server::helpers::MethodSink; use crate::traits::{IdProvider, ToRpcParams}; -use futures_util::future::Either; -use futures_util::pin_mut; -use futures_util::{future::BoxFuture, FutureExt, Stream, StreamExt, TryStream, TryStreamExt}; -use jsonrpsee_types::error::{ - CallError, ErrorCode, ErrorObject, ErrorObjectOwned, SubscriptionAcceptRejectError, INTERNAL_ERROR_CODE, - SUBSCRIPTION_CLOSED_WITH_ERROR, -}; +use futures_util::{future::BoxFuture, FutureExt}; +use jsonrpsee_types::error::{CallError, ErrorCode, ErrorObject, ErrorObjectOwned, SubscriptionAcceptRejectError}; use jsonrpsee_types::response::{SubscriptionError, SubscriptionPayloadError}; use jsonrpsee_types::{ ErrorResponse, Id, Params, Request, Response, SubscriptionId as RpcSubscriptionId, SubscriptionPayload, @@ -49,7 +44,7 @@ use jsonrpsee_types::{ use parking_lot::Mutex; use rustc_hash::FxHashMap; use serde::{de::DeserializeOwned, Serialize}; -use tokio::sync::{mpsc, oneshot, watch, Notify}; +use tokio::sync::{mpsc, oneshot, watch}; use super::helpers::MethodResponse; @@ -74,25 +69,22 @@ pub type ConnectionId = usize; /// Max response size. pub type MaxResponseSize = usize; -/// A future the returns when the connection has been closed. -pub type CloseNotify = Arc; - /// Raw response from an RPC /// A 3-tuple containing: /// - Call result as a `String`, /// - a [`mpsc::Receiver`] to receive future subscription results -/// - a [`CloseNotify`] to allow subscribers to notify their [`SubscriptionSink`] when they disconnect. -pub type RawRpcResponse = (MethodResponse, mpsc::Receiver, CloseNotify); +pub type RawRpcResponse = (MethodResponse, mpsc::Receiver, MethodSink); /// Disconnect error. pub type DisconnectError = mpsc::error::SendError; +/// TrySendError +pub type TrySendError = mpsc::error::TrySendError; + /// Helper struct to manage subscriptions. pub struct ConnState<'a> { /// Connection ID pub conn_id: ConnectionId, - /// Get notified when the connection to subscribers is closed. - pub close_notify: CloseNotify, /// ID provider. pub id_provider: &'a dyn IdProvider, } @@ -115,15 +107,33 @@ pub enum SubscriptionSinkError { Subscribe(SubscriptionAcceptRejectError), /// The connection is closed. #[error("The connection is closed")] - Disconnected, + Disconnected(String), + /// The subscription is full, could not send the message. + #[error("The subscription buffer is full")] + Full(String), #[error("{0}")] /// Something when trying to decode the message. Serialize(#[from] serde_json::Error), } +impl From for SubscriptionSinkError { + fn from(e: TrySendError) -> Self { + match e { + TrySendError::Closed(m) => Self::Disconnected(m), + TrySendError::Full(m) => Self::Full(m), + } + } +} + +impl From> for SubscriptionSinkError { + fn from(e: DisconnectError) -> Self { + Self::Disconnected(e.0) + } +} + impl<'a> std::fmt::Debug for ConnState<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ConnState").field("conn_id", &self.conn_id).field("close", &self.close_notify).finish() + f.debug_struct("ConnState").field("conn_id", &self.conn_id).finish() } } @@ -355,15 +365,13 @@ impl Methods { let sink = MethodSink::new(tx_sink); let id = req.id.clone(); let params = Params::new(req.params.map(|params| params.get())); - let close_notify = Arc::new(Notify::new()); let response = match self.method(&req.method).map(|c| &c.callback) { None => MethodResponse::error(req.id, ErrorObject::from(ErrorCode::MethodNotFound)), Some(MethodKind::Sync(cb)) => (cb)(id, params, usize::MAX), Some(MethodKind::Async(cb)) => (cb)(id.into_owned(), params.into_owned(), 0, usize::MAX).await, Some(MethodKind::Subscription(cb)) => { - let conn_state = - ConnState { conn_id: 0, close_notify: close_notify.clone(), id_provider: &RandomIntegerIdProvider }; + let conn_state = ConnState { conn_id: 0, id_provider: &RandomIntegerIdProvider }; let res = (cb)(id, params, sink.clone(), conn_state).await; // This message is not used because it's used for metrics so we discard in other to @@ -379,7 +387,7 @@ impl Methods { tracing::trace!("[Methods::inner_call] Method: {}, response: {:?}", req.method, response); - (response, rx_sink, close_notify) + (response, rx_sink, sink) } /// Helper to create a subscription on the `RPC module` without having to spin up a server. @@ -413,7 +421,7 @@ impl Methods { tracing::trace!("[Methods::subscribe] Method: {}, params: {:?}", sub_method, params); - let (response, rx, close_notify) = self.inner_call(req).await; + let (response, rx, tx) = self.inner_call(req).await; let subscription_response = match serde_json::from_str::>(&response.result) { Ok(r) => r, @@ -424,9 +432,8 @@ impl Methods { }; let sub_id = subscription_response.result.into_owned(); - let close_notify = Some(close_notify); - Ok(Subscription { sub_id, rx, close_notify }) + Ok(Subscription { sub_id, rx, tx }) } /// Returns an `Iterator` with all the method names registered on this server. @@ -683,12 +690,8 @@ impl RpcModule { // response to the subscription call. let (tx, rx) = oneshot::channel(); - let queue = Arc::new(crossbeam_queue::ArrayQueue::::new(4)); - let sink = SubscriptionSink { inner: method_sink.clone(), - queue: queue.clone(), - close_notify: conn.close_notify, method: notif_method_name, subscribers: subscribers.clone(), uniq_sub, @@ -696,14 +699,6 @@ impl RpcModule { unsubscribe: None, }; - tokio::spawn(async move { - while let Ok(permit) = method_sink.reserve().await { - if let Some(val) = queue.pop() { - permit.send_raw(val); - } - } - }); - // The callback returns a `SubscriptionResult` for better ergonomics and is not propagated further. if callback(params, sink, ctx.clone()).is_err() { tracing::warn!("Subscribe call `{}` failed", subscribe_method_name); @@ -749,17 +744,13 @@ type UnsubscribeCall = Option>; pub struct SubscriptionSink { /// Sink. inner: MethodSink, - /// Queue of messages that are waiting to be sent. - queue: Arc>, - /// Get notified when subscribers leave so we can exit - close_notify: CloseNotify, /// MethodCallback. method: &'static str, /// Shared Mutex of subscriptions for this method. subscribers: Subscribers, /// Unique subscription. uniq_sub: SubscriptionKey, - /// Id of the `subscription call` (i.e. not the same as subscription id) which is used + /// ID of the `subscription call` (i.e. not the same as subscription id) which is used /// to reply to subscription method call and must only be used once. /// /// *Note*: Having some value means the subscription was not accepted or rejected yet. @@ -813,150 +804,46 @@ impl SubscriptionSink { } } - /// Send a message back to subscribers. + /// Send a message back to subscribers asyncronously. /// /// Returns /// - `Ok(())` if the message could be sent. - /// - `Err(err)` if an error occurred when trying to send the message. - pub async fn send(&mut self, result: &T) -> Result<(), SubscriptionSinkError> { + /// - `Err(err)` if the connection or subscription was closed. + pub async fn send(&mut self, msg: String) -> Result<(), SubscriptionSinkError> { match self.accept().await { Ok(_) => (), Err(SubscriptionAcceptRejectError::AlreadyCalled) => (), - Err(SubscriptionAcceptRejectError::RemotePeerAborted) => return Err(SubscriptionSinkError::Disconnected), + Err(SubscriptionAcceptRejectError::RemotePeerAborted) => { + return Err(SubscriptionSinkError::Disconnected(msg)) + } }; - self.send_without_accept(result) + // Only possible to trigger when the connection is dropped. + if self.is_closed() { + return Err(SubscriptionSinkError::Disconnected(msg)); + } + + self.inner.send(msg).await.map_err(Into::into) } - /// Reads data from the `stream` and sends back data on the subscription - /// when items gets produced by the stream. - /// The underlying stream must produce `Result values, see [`futures_util::TryStream`] for further information. - /// - /// Returns `Ok(())` if the stream or connection was terminated. - /// Returns `Err(_)` immediately if the underlying stream returns an error or if an item from the stream could not be serialized. - /// - /// # Examples - /// - /// ```no_run - /// - /// use jsonrpsee_core::server::rpc_module::RpcModule; - /// use jsonrpsee_core::error::{Error, SubscriptionClosed}; - /// use jsonrpsee_types::ErrorObjectOwned; - /// use anyhow::anyhow; - /// - /// let mut m = RpcModule::new(()); - /// m.register_subscription("sub", "_", "unsub", |params, mut sink, _| { - /// let stream = futures_util::stream::iter(vec![Ok(1_u32), Ok(2), Err("error on the stream")]); - /// // This will return send `[Ok(1_u32), Ok(2_u32), Err(Error::SubscriptionClosed))]` to the subscriber - /// // because after the `Err(_)` the stream is terminated. - /// let stream = futures_util::stream::iter(vec![Ok(1_u32), Ok(2), Err("error on the stream")]); + /// Attempts to immediately send out the message to the subscribers but fails if the + /// channel is full or that the connection is closed. /// - /// tokio::spawn(async move { + /// This is useful if you want to replace to old messages and keep your own buffer. /// - /// // jsonrpsee doesn't send an error notification unless `close` is explicitly called. - /// // If we pipe messages to the sink, we can inspect why it ended: - /// match sink.pipe_from_try_stream(stream).await { - /// SubscriptionClosed::Success => { - /// let err_obj: ErrorObjectOwned = SubscriptionClosed::Success.into(); - /// sink.close(err_obj); - /// } - /// // we don't want to send close reason when the client is unsubscribed or disconnected. - /// SubscriptionClosed::RemotePeerAborted => (), - /// SubscriptionClosed::Failed(e) => { - /// sink.close(e); - /// } - /// } - /// }); - /// Ok(()) - /// }); - /// ``` - pub async fn pipe_from_try_stream(&mut self, mut stream: S) -> SubscriptionClosed - where - S: TryStream + Unpin, - T: Serialize, - E: std::fmt::Display, - { - if let Err(SubscriptionAcceptRejectError::RemotePeerAborted) = self.accept().await { - return SubscriptionClosed::RemotePeerAborted; + /// This differs from [`SubscriptionSink::send`] as it will until there is capacity + /// in the channel. + pub fn try_send(&mut self, msg: String) -> Result<(), SubscriptionSinkError> { + if self.id.is_some() { + todo!(); } - let conn_closed = self.close_notify.clone(); - - let mut sub_closed = match self.unsubscribe.as_ref() { - Some(rx) => rx.clone(), - _ => { - return SubscriptionClosed::Failed(ErrorObject::owned( - INTERNAL_ERROR_CODE, - "Unsubscribe watcher not set after accepting the subscription".to_string(), - None::<()>, - )) - } - }; - - let sub_closed_fut = sub_closed.changed(); - - let conn_closed_fut = conn_closed.notified(); - pin_mut!(conn_closed_fut); - pin_mut!(sub_closed_fut); - - let mut stream_item = stream.try_next(); - let mut closed_fut = futures_util::future::select(conn_closed_fut, sub_closed_fut); - - loop { - match futures_util::future::select(stream_item, closed_fut).await { - // The app sent us a value to send back to the subscribers - Either::Left((Ok(Some(result)), next_closed_fut)) => { - match self.send_without_accept(&result) { - Ok(()) => (), - Err(SubscriptionSinkError::Disconnected) => { - break SubscriptionClosed::RemotePeerAborted; - } - Err(err) => { - let err = ErrorObject::owned(SUBSCRIPTION_CLOSED_WITH_ERROR, err.to_string(), None::<()>); - break SubscriptionClosed::Failed(err); - } - }; - stream_item = stream.try_next(); - closed_fut = next_closed_fut; - } - // Stream canceled because of error. - Either::Left((Err(err), _)) => { - let err = ErrorObject::owned(SUBSCRIPTION_CLOSED_WITH_ERROR, err.to_string(), None::<()>); - break SubscriptionClosed::Failed(err); - } - Either::Left((Ok(None), _)) => break SubscriptionClosed::Success, - Either::Right((_, _)) => { - break SubscriptionClosed::RemotePeerAborted; - } - } + // Only possible to trigger when the connection is dropped. + if self.is_closed() { + return Err(SubscriptionSinkError::Disconnected(msg)); } - } - /// Similar to [`SubscriptionSink::pipe_from_try_stream`] but it doesn't require the stream return `Result`. - /// - /// Warning: it's possible to pass in a stream that returns `Result` if `Result: Serialize` is satisfied - /// but it won't cancel the stream when an error occurs. If you want the stream to be canceled when an - /// error occurs use [`SubscriptionSink::pipe_from_try_stream`] instead. - /// - /// # Examples - /// - /// ```no_run - /// - /// use jsonrpsee_core::server::rpc_module::RpcModule; - /// - /// let mut m = RpcModule::new(()); - /// m.register_subscription("sub", "_", "unsub", |params, mut sink, _| { - /// let stream = futures_util::stream::iter(vec![1_usize, 2, 3]); - /// tokio::spawn(async move { sink.pipe_from_stream(stream).await; }); - /// Ok(()) - /// }); - /// ``` - pub async fn pipe_from_stream(&mut self, stream: S) -> SubscriptionClosed - where - S: Stream + Unpin, - T: Serialize, - { - self.pipe_from_try_stream::<_, _, Error>(stream.map(|item| Ok(item))).await + self.inner.try_send(msg).map_err(Into::into) } /// Returns whether the subscription is closed. @@ -964,23 +851,13 @@ impl SubscriptionSink { self.inner.is_closed() || !self.is_active_subscription() } - /// Send a message back to subscribers. - /// - /// This is similar to the [`SubscriptionSink::send`], but it does not try to accept - /// the subscription prior to sending. - #[inline] - fn send_without_accept(&mut self, result: &T) -> Result<(), SubscriptionSinkError> { - // Only possible to trigger when the connection is dropped. - if self.is_closed() { - return Err(SubscriptionSinkError::Disconnected); + /// See [`MethodSink::closed`] for documentation. + pub async fn closed(&self) { + if !self.is_active_subscription() { + return; } - let msg = self.build_message(result)?; - if let Some(rm) = self.queue.force_push(msg) { - tracing::debug!("Client can't keep up with subscription notifs; replaced old message: {:?}", rm); - } - - Ok(()) + self.inner.closed().await } fn is_active_subscription(&self) -> bool { @@ -1000,7 +877,8 @@ impl SubscriptionSink { subscribe_call.send(response).map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted) } - fn build_message(&self, result: &T) -> Result { + /// ... + pub fn build_message(&self, result: &T) -> Result { serde_json::to_string(&SubscriptionResponse::new( self.method.into(), SubscriptionPayload { subscription: self.uniq_sub.sub_id.clone(), result }, @@ -1008,7 +886,8 @@ impl SubscriptionSink { .map_err(Into::into) } - fn build_error_message(&self, error: &T) -> Result { + /// ... + pub fn build_error_message(&self, error: &T) -> Result { serde_json::to_string(&SubscriptionError::new( self.method.into(), SubscriptionPayloadError { subscription: self.uniq_sub.sub_id.clone(), error }, @@ -1036,46 +915,49 @@ impl SubscriptionSink { /// } /// ``` /// - pub fn close(self, err: impl Into) -> impl Future { + pub fn close(mut self, err: impl Into) -> impl Future { + if let Some((id, subscribe_call)) = self.id.take() { + // Subscription was never accepted / rejected. As such, + // we default to assuming that the params were invalid, + // because that's how the previous PendingSubscription logic + // worked. + let err = MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidParams)); + return async move { + let _ = self.answer_subscription(err, subscribe_call).await; + } + .boxed(); + } + if self.is_active_subscription() { if let Some((sink, _)) = self.subscribers.lock().remove(&self.uniq_sub) { tracing::debug!("Closing subscription: {:?}", self.uniq_sub.sub_id); let msg = self.build_error_message(&err.into()).expect("valid json infallible; qed"); - return Either::Right(async move { + return async move { let permit = match sink.reserve().await { Ok(permit) => permit, - Err(_) => return false, + Err(_) => return, }; permit.send_raw(msg); - true - }); + } + .boxed(); } } - Either::Left(futures_util::future::ready(false)) + futures_util::future::ready(()).boxed() } } impl Drop for SubscriptionSink { fn drop(&mut self) { - if let Some((id, subscribe_call)) = self.id.take() { - // Subscription was never accepted / rejected. As such, - // we default to assuming that the params were invalid, - // because that's how the previous PendingSubscription logic - // worked. - let err = MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidParams)); - let _ = self.answer_subscription(err, subscribe_call).now_or_never(); - } else if self.is_active_subscription() { - self.subscribers.lock().remove(&self.uniq_sub); - } + self.subscribers.lock().remove(&self.uniq_sub); } } /// Wrapper struct that maintains a subscription "mainly" for testing. #[derive(Debug)] pub struct Subscription { - close_notify: Option, + tx: MethodSink, rx: mpsc::Receiver, sub_id: RpcSubscriptionId<'static>, } @@ -1084,9 +966,7 @@ impl Subscription { /// Close the subscription channel. pub fn close(&mut self) { tracing::trace!("[Subscription::close] Notifying"); - if let Some(n) = self.close_notify.take() { - n.notify_one() - } + self.rx.close(); } /// Get the subscription ID @@ -1096,7 +976,7 @@ impl Subscription { /// Check whether the subscription is closed. pub fn is_closed(&self) -> bool { - self.close_notify.is_none() + self.tx.is_closed() } /// Returns `Some((val, sub_id))` for the next element of type T from the underlying stream, @@ -1106,10 +986,6 @@ impl Subscription { /// /// If the decoding the value as `T` fails. pub async fn next(&mut self) -> Option), Error>> { - if self.close_notify.is_none() { - tracing::debug!("[Subscription::next] Closed."); - return None; - } let raw = self.rx.recv().await?; tracing::debug!("[Subscription::next]: rx {}", raw); diff --git a/examples/examples/ws_pubsub_broadcast.rs b/examples/examples/ws_pubsub_broadcast.rs index 6305fc8870..90cfc83904 100644 --- a/examples/examples/ws_pubsub_broadcast.rs +++ b/examples/examples/ws_pubsub_broadcast.rs @@ -31,7 +31,6 @@ use std::net::SocketAddr; use futures::future; use futures::StreamExt; use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; -use jsonrpsee::core::error::SubscriptionClosed; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, ServerBuilder}; use jsonrpsee::ws_client::WsClientBuilder; diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index b2aea136d1..22a9746573 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -29,7 +29,6 @@ use std::time::Duration; use futures::StreamExt; use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; -use jsonrpsee::core::error::SubscriptionClosed; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, ServerBuilder}; use jsonrpsee::ws_client::WsClientBuilder; @@ -73,11 +72,17 @@ async fn run_server() -> anyhow::Result { let item = LETTERS.chars().nth(idx); let interval = interval(Duration::from_millis(200)); - let stream = IntervalStream::new(interval).map(move |_| item); + let mut stream = IntervalStream::new(interval).map(move |_| item); tokio::spawn(async move { - let res = sink.pipe_from_stream(stream).await; - tracing::info!("subscription result: {:?}", res); + sink.accept().await.unwrap(); + + while let Some(item) = stream.next().await { + let notif = sink.build_message(&item).unwrap(); + if let Err(e) = sink.try_send(notif) { + tracing::info!("ignoring to send notif: {:?}", e); + } + } }); Ok(()) }) @@ -89,11 +94,17 @@ async fn run_server() -> anyhow::Result { let item = &LETTERS[one..two]; let interval = interval(Duration::from_millis(200)); - let stream = IntervalStream::new(interval).map(move |_| item); + let mut stream = IntervalStream::new(interval).map(move |_| item); tokio::spawn(async move { - let res = sink.pipe_from_stream(stream).await; - tracing::info!("subscription result: {:?}", res); + sink.accept().await.unwrap(); + + while let Some(item) = stream.next().await { + let notif = sink.build_message(&item).unwrap(); + if let Err(e) = sink.try_send(notif) { + tracing::info!("ignoring to send notif: {:?}", e); + } + } }); Ok(()) diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 5b8d7d7a1a..9ccb53863b 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -1,5 +1,4 @@ use std::pin::Pin; -use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; @@ -23,7 +22,7 @@ use jsonrpsee_types::error::{ use jsonrpsee_types::{ErrorObject, Id, InvalidRequest, Notification, Params, Request}; use soketto::connection::Error as SokettoError; use soketto::data::ByteSlice125; -use tokio::sync::{mpsc, Notify}; +use tokio::sync::{mpsc, oneshot}; use tokio_stream::wrappers::{IntervalStream, ReceiverStream}; use tokio_util::compat::Compat; use tracing::instrument; @@ -62,7 +61,6 @@ pub(crate) struct CallData<'a, L: Logger> { pub(crate) sink: &'a MethodSink, pub(crate) logger: &'a L, pub(crate) request_start: L::Instant, - pub(crate) close_notify: Arc, } /// This is a glorified select listening for new messages, while also checking the `stop_receiver` signal. @@ -169,17 +167,8 @@ pub(crate) async fn execute_call_with_tracing<'a, L: Logger>(req: Request<'a>, c /// Returns `(MethodResponse, None)` on every call that isn't a subscription /// Otherwise `(MethodResponse, Some(PendingSubscriptionCallTx)`. pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData<'_, L>) -> MethodResult { - let CallData { - methods, - max_response_body_size, - max_log_length, - conn_id, - id_provider, - sink, - logger, - request_start, - close_notify, - } = call; + let CallData { methods, max_response_body_size, max_log_length, conn_id, id_provider, sink, logger, request_start } = + call; rx_log_from_json(&req, call.max_log_length); @@ -210,7 +199,7 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData MethodKind::Subscription(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::Subscription, TransportProtocol::WebSocket); - let conn_state = ConnState { conn_id, close_notify, id_provider }; + let conn_state = ConnState { conn_id, id_provider }; let response = callback(id.clone(), params, sink.clone(), conn_state).await; MethodResult::JustLogger(response) } @@ -254,11 +243,11 @@ pub(crate) async fn background_task( } = svc; let (tx, rx) = mpsc::channel::(buffer_capacity as usize); + let (conn_tx, conn_rx) = oneshot::channel(); let sink = MethodSink::new_with_limit(tx, max_response_body_size, max_log_length); - let close_notify = Arc::new(Notify::new()); // Spawn another task that sends out the responses on the Websocket. - tokio::spawn(send_task(rx, sender, stop_handle.clone(), ping_interval)); + tokio::spawn(send_task(rx, sender, stop_handle.clone(), ping_interval, conn_rx)); // Buffer for incoming data. let mut data = Vec::with_capacity(100); @@ -336,7 +325,6 @@ pub(crate) async fn background_task( let sink = sink.clone(); let methods = &methods; let id_provider = &*id_provider; - let close_notify = close_notify.clone(); let fut = async move { let call = CallData { @@ -348,7 +336,6 @@ pub(crate) async fn background_task( id_provider, logger, request_start, - close_notify: close_notify.clone(), }; match process_single_request(data, call).await { @@ -379,7 +366,6 @@ pub(crate) async fn background_task( let sink = sink.clone(); let id_provider = id_provider.clone(); let data = std::mem::take(&mut data); - let close_notify = close_notify.clone(); let fut = async move { let response = process_batch_request(Batch { @@ -393,7 +379,6 @@ pub(crate) async fn background_task( id_provider: &*id_provider, logger, request_start, - close_notify, }, }) .await; @@ -420,8 +405,7 @@ pub(crate) async fn background_task( // proper drop behaviour. method_executors.await; - // Notify all listeners and close down associated tasks. - close_notify.notify_waiters(); + let _ = conn_tx.send(()); drop(conn); result @@ -433,19 +417,22 @@ async fn send_task( mut ws_sender: Sender, mut stop_handle: StopHandle, ping_interval: Duration, + conn_closed: oneshot::Receiver<()>, ) { - /* + // fake that no messages were read. + //future::pending::<()>().await; + // Interval to send out continuously `pings`. let ping_interval = IntervalStream::new(tokio::time::interval(ping_interval)); let stopped = stop_handle.shutdown(); let rx = ReceiverStream::new(rx); - tokio::pin!(ping_interval, stopped, rx); + tokio::pin!(ping_interval, stopped, rx, conn_closed); // Received messages from the WebSocket. let mut rx_item = rx.next(); let next_ping = ping_interval.next(); - let mut futs = future::select(next_ping, stopped); + let mut futs = future::select(next_ping, future::select(stopped, conn_closed)); loop { // Ensure select is cancel-safe by fetching and storing the `rx_item` that did not finish yet. @@ -478,16 +465,14 @@ async fn send_task( futs = future::select(ping_interval.next(), stop); } - // Server is closed - Either::Right((Either::Right((_, _)), _)) => { + // Server is stopped or closed + Either::Right((Either::Right(_), _)) => { break; } } - }*/ - - // fake that no messages were read. - future::pending::<()>().await; + } // Terminate connection and send close message. let _ = ws_sender.close().await; + rx.close(); } diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 4198bd4f83..6668a527f5 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -40,7 +40,7 @@ use helpers::{ use hyper::http::HeaderValue; use jsonrpsee::core::client::{ClientT, IdKind, Subscription, SubscriptionClientT}; use jsonrpsee::core::params::{ArrayParams, BatchRequestBuilder}; -use jsonrpsee::core::server::rpc_module::{DisconnectError, SubscriptionSinkError}; +use jsonrpsee::core::server::rpc_module::SubscriptionSinkError; use jsonrpsee::core::{Error, JsonValue}; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::rpc_params; From 2599b8f5bbbea498c064d3d264f9643615a70750 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 16 Jan 2023 18:50:51 +0100 Subject: [PATCH 11/60] bring back Pending and SubscriptionSink again --- benches/helpers.rs | 8 +- core/Cargo.toml | 2 - core/src/error.rs | 31 +++- core/src/server/helpers.rs | 6 +- core/src/server/rpc_module.rs | 199 +++++++++------------ examples/examples/proc_macro.rs | 12 +- examples/examples/ws_pubsub_broadcast.rs | 40 +++-- examples/examples/ws_pubsub_with_params.rs | 45 +++-- jsonrpsee/Cargo.toml | 3 +- jsonrpsee/src/lib.rs | 3 +- proc-macros/src/render_server.rs | 27 +-- server/src/lib.rs | 2 +- server/src/tests/helpers.rs | 6 +- server/src/tests/ws.rs | 13 +- server/src/transport/ws.rs | 9 +- tests/tests/helpers.rs | 138 +++++++------- tests/tests/integration_tests.rs | 74 +------- tests/tests/proc_macros.rs | 32 +++- tests/tests/rpc_module.rs | 118 ++++++------ types/src/error.rs | 2 + 20 files changed, 374 insertions(+), 396 deletions(-) diff --git a/benches/helpers.rs b/benches/helpers.rs index 91a64e989d..fa8b7602a8 100644 --- a/benches/helpers.rs +++ b/benches/helpers.rs @@ -146,9 +146,13 @@ pub async fn ws_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee::se let mut module = gen_rpc_module(); module - .register_subscription(SUB_METHOD_NAME, SUB_METHOD_NAME, UNSUB_METHOD_NAME, |_params, mut sink, _ctx| { + .register_subscription(SUB_METHOD_NAME, SUB_METHOD_NAME, UNSUB_METHOD_NAME, |_params, pending, _ctx| { let x = "Hello"; - tokio::spawn(async move { sink.send(&x).await }); + tokio::spawn(async move { + let sink = pending.accept().await.unwrap(); + let msg = sink.build_message(&x).unwrap(); + sink.send(msg).await.unwrap(); + }); Ok(()) }) .unwrap(); diff --git a/core/Cargo.toml b/core/Cargo.toml index a4ea27692c..c1c95c20a3 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -31,7 +31,6 @@ wasm-bindgen-futures = { version = "0.4.19", optional = true } futures-timer = { version = "3", optional = true } globset = { version = "0.4", optional = true } http = { version = "0.2.7", optional = true } -crossbeam-queue = { version = "0.3", optional = true } [features] default = [] @@ -45,7 +44,6 @@ server = [ "rand", "tokio/rt", "tokio/sync", - "crossbeam-queue", ] client = ["futures-util/sink", "futures-channel/sink"] async-client = [ diff --git a/core/src/error.rs b/core/src/error.rs index 9c32156ca5..a6864dd45f 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -27,7 +27,8 @@ use std::fmt; use jsonrpsee_types::error::{ - CallError, ErrorObject, ErrorObjectOwned, CALL_EXECUTION_FAILED_CODE, INVALID_PARAMS_CODE, UNKNOWN_ERROR_CODE, + CallError, ErrorObject, ErrorObjectOwned, CALL_EXECUTION_FAILED_CODE, INVALID_PARAMS_CODE, SUBSCRIPTION_CLOSED, + UNKNOWN_ERROR_CODE, }; /// Convenience type for displaying errors. @@ -148,6 +149,34 @@ impl From for ErrorObjectOwned { } } +/// A type to represent when a subscription gets closed +/// by either the server or client side. +#[derive(Clone, Debug)] +pub enum SubscriptionClosed { + /// The remote peer closed the connection or called the unsubscribe method. + RemotePeerAborted, + /// The subscription was completed successfully by the server. + Success, + /// The subscription failed during execution by the server. + Failed(ErrorObject<'static>), +} + +impl From for ErrorObjectOwned { + fn from(err: SubscriptionClosed) -> Self { + match err { + SubscriptionClosed::RemotePeerAborted => { + ErrorObject::owned(SUBSCRIPTION_CLOSED, "Subscription was closed by the remote peer", None::<()>) + } + SubscriptionClosed::Success => ErrorObject::owned( + SUBSCRIPTION_CLOSED, + "Subscription was completed by the server successfully", + None::<()>, + ), + SubscriptionClosed::Failed(err) => err, + } + } +} + /// Generic transport error. #[derive(Debug, thiserror::Error)] pub enum GenericTransportError { diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index b352adb9de..90dbedde73 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -121,19 +121,21 @@ impl MethodSink { /// /// Returns the message if the send fails such that either can be thrown away or re-sent later. pub fn try_send(&mut self, msg: String) -> Result<(), TrySendError> { + tx_log_from_str(&msg, self.max_log_length); self.tx.try_send(msg) } /// Async send which will wait until there is space in channel buffer or that the subscription is disconnected. pub async fn send(&self, msg: String) -> Result<(), DisconnectError> { - self.tx.send(msg).await + tx_log_from_str(&msg, self.max_log_length); + self.tx.send(msg).await.map_err(Into::into) } /// Waits for channel capacity. Once capacity to send one message is available, it is reserved for the caller. pub async fn reserve(&self) -> Result> { match self.tx.reserve().await { Ok(permit) => Ok(MethodSinkPermit { tx: permit, max_log_length: self.max_log_length }), - Err(e) => Err(e), + Err(e) => Err(e.into()), } } } diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 503526115f..27ad58aa68 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -46,7 +46,7 @@ use rustc_hash::FxHashMap; use serde::{de::DeserializeOwned, Serialize}; use tokio::sync::{mpsc, oneshot, watch}; -use super::helpers::MethodResponse; +use super::helpers::{MethodResponse, MethodSinkPermit}; /// A `MethodCallback` is an RPC endpoint, callable with a standard JSON-RPC request, /// implemented as a function pointer to a `Fn` function taking four arguments: @@ -75,12 +75,13 @@ pub type MaxResponseSize = usize; /// - a [`mpsc::Receiver`] to receive future subscription results pub type RawRpcResponse = (MethodResponse, mpsc::Receiver, MethodSink); -/// Disconnect error. -pub type DisconnectError = mpsc::error::SendError; - /// TrySendError pub type TrySendError = mpsc::error::TrySendError; +/// Disconnect error +#[derive(Debug)] +pub struct DisconnectError(pub T); + /// Helper struct to manage subscriptions. pub struct ConnState<'a> { /// Connection ID @@ -98,36 +99,9 @@ pub enum InnerSubscriptionResult { Aborted, } -/// Sending stuff via a subscription can either fail if the subscription failed or that -/// actual connection is full or disconnected. -#[derive(Debug, thiserror::Error)] -pub enum SubscriptionSinkError { - /// Something failed during the init of the subscription. - #[error("{0:?}")] - Subscribe(SubscriptionAcceptRejectError), - /// The connection is closed. - #[error("The connection is closed")] - Disconnected(String), - /// The subscription is full, could not send the message. - #[error("The subscription buffer is full")] - Full(String), - #[error("{0}")] - /// Something when trying to decode the message. - Serialize(#[from] serde_json::Error), -} - -impl From for SubscriptionSinkError { - fn from(e: TrySendError) -> Self { - match e { - TrySendError::Closed(m) => Self::Disconnected(m), - TrySendError::Full(m) => Self::Full(m), - } - } -} - -impl From> for SubscriptionSinkError { - fn from(e: DisconnectError) -> Self { - Self::Disconnected(e.0) +impl From> for DisconnectError { + fn from(e: mpsc::error::SendError) -> Self { + DisconnectError(e.0) } } @@ -139,6 +113,10 @@ impl<'a> std::fmt::Debug for ConnState<'a> { type Subscribers = Arc)>>>; +/// Subscription message. +#[derive(Debug, Clone)] +pub struct SubscriptionMessage(String); + /// Represent a unique subscription entry based on [`RpcSubscriptionId`] and [`ConnectionId`]. #[derive(Clone, Debug, PartialEq, Eq, Hash)] struct SubscriptionKey { @@ -632,7 +610,7 @@ impl RpcModule { ) -> Result<&mut MethodCallback, Error> where Context: Send + Sync + 'static, - F: Fn(Params, SubscriptionSink, Arc) -> SubscriptionResult + Send + Sync + 'static, + F: Fn(Params, PendingSubscriptionSink, Arc) -> SubscriptionResult + Send + Sync + 'static, { if subscribe_method_name == unsubscribe_method_name { return Err(Error::SubscriptionNameConflict(subscribe_method_name.into())); @@ -690,12 +668,12 @@ impl RpcModule { // response to the subscription call. let (tx, rx) = oneshot::channel(); - let sink = SubscriptionSink { + let sink = PendingSubscriptionSink { inner: method_sink.clone(), method: notif_method_name, subscribers: subscribers.clone(), uniq_sub, - id: Some((id.clone().into_owned(), tx)), + id: (id.clone().into_owned(), tx), unsubscribe: None, }; @@ -739,9 +717,11 @@ impl RpcModule { /// Returns once the unsubscribe method has been called. type UnsubscribeCall = Option>; -/// Represents a single subscription. +/// Represents a single subscription that is waiting to be accepted or rejected. +/// +/// You must either call `accept` or `reject` otherwise this does nothing. #[derive(Debug)] -pub struct SubscriptionSink { +pub struct PendingSubscriptionSink { /// Sink. inner: MethodSink, /// MethodCallback. @@ -752,56 +732,87 @@ pub struct SubscriptionSink { uniq_sub: SubscriptionKey, /// ID of the `subscription call` (i.e. not the same as subscription id) which is used /// to reply to subscription method call and must only be used once. - /// - /// *Note*: Having some value means the subscription was not accepted or rejected yet. - id: Option<(Id<'static>, oneshot::Sender)>, + id: (Id<'static>, oneshot::Sender), /// Having some value means the subscription was accepted. unsubscribe: UnsubscribeCall, } -impl SubscriptionSink { - /// Reject the subscription call from [`ErrorObject`]. - pub async fn reject(&mut self, err: impl Into) -> Result<(), SubscriptionAcceptRejectError> { - let (id, subscribe_call) = self.id.take().ok_or(SubscriptionAcceptRejectError::AlreadyCalled)?; +impl PendingSubscriptionSink { + /// Reject the subscription call with the error from [`ErrorObject`]. + pub async fn reject(self, err: impl Into) -> Result<(), SubscriptionAcceptRejectError> { + let (id, subscribe_call) = self.id; let err = MethodResponse::error(id, err.into()); + let permit = self.inner.reserve().await.map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; - self.answer_subscription(err, subscribe_call).await?; + Self::answer_subscription(permit, err, subscribe_call).await?; Ok(()) } /// Attempt to accept the subscription and respond the subscription method call. /// - /// Fails if the connection was closed, or if called multiple times. - pub async fn accept(&mut self) -> Result<(), SubscriptionAcceptRejectError> { - let (id, subscribe_call) = self.id.take().ok_or(SubscriptionAcceptRejectError::AlreadyCalled)?; + /// Fails if the connection was closed or the message was too large. + pub async fn accept(mut self) -> Result { + let (id, subscribe_call) = self.id; let response = MethodResponse::response(id, &self.uniq_sub.sub_id, self.inner.max_response_size() as usize); let success = response.success; + let permit = self.inner.reserve().await.map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; - self.answer_subscription(response, subscribe_call).await?; + Self::answer_subscription(permit, response, subscribe_call).await?; if success { let (tx, rx) = watch::channel(()); self.subscribers.lock().insert(self.uniq_sub.clone(), (self.inner.clone(), tx)); self.unsubscribe = Some(rx); - Ok(()) + Ok(SubscriptionSink { + inner: self.inner, + method: self.method, + subscribers: self.subscribers, + uniq_sub: self.uniq_sub, + unsubscribe: self.unsubscribe, + }) } else { - // TODO(niklasad1): this is wrong, the response was too big to be sent. - Err(SubscriptionAcceptRejectError::RemotePeerAborted) + Err(SubscriptionAcceptRejectError::MessageTooLarge) } } - /// Return the subscription ID if the the subscription was accepted. + async fn answer_subscription<'a>( + permit: MethodSinkPermit<'a>, + response: MethodResponse, + subscribe_call: oneshot::Sender, + ) -> Result<(), SubscriptionAcceptRejectError> { + permit.send_raw(response.result.clone()); + subscribe_call.send(response).map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted) + } +} + +/// Represents a single subscription that hasn't been processed yet. +#[derive(Debug)] +pub struct SubscriptionSink { + /// Sink. + inner: MethodSink, + /// MethodCallback. + method: &'static str, + /// Shared Mutex of subscriptions for this method. + subscribers: Subscribers, + /// Unique subscription. + uniq_sub: SubscriptionKey, + /// Having some value means the subscription was accepted. + unsubscribe: UnsubscribeCall, +} + +impl SubscriptionSink { + /// Get the subscription ID. /// /// [`SubscriptionSink::accept`] should be called prior to this method. - pub fn subscription_id(&self) -> Option> { - if self.id.is_some() { - // Subscription was not accepted. - None - } else { - Some(self.uniq_sub.sub_id.clone()) - } + pub fn subscription_id(&self) -> RpcSubscriptionId<'static> { + self.uniq_sub.sub_id.clone() + } + + /// Get the method name. + pub fn method_name(&self) -> &str { + self.method } /// Send a message back to subscribers asyncronously. @@ -809,38 +820,26 @@ impl SubscriptionSink { /// Returns /// - `Ok(())` if the message could be sent. /// - `Err(err)` if the connection or subscription was closed. - pub async fn send(&mut self, msg: String) -> Result<(), SubscriptionSinkError> { - match self.accept().await { - Ok(_) => (), - Err(SubscriptionAcceptRejectError::AlreadyCalled) => (), - Err(SubscriptionAcceptRejectError::RemotePeerAborted) => { - return Err(SubscriptionSinkError::Disconnected(msg)) - } - }; - + pub async fn send(&self, msg: String) -> Result<(), DisconnectError> { // Only possible to trigger when the connection is dropped. if self.is_closed() { - return Err(SubscriptionSinkError::Disconnected(msg)); + return Err(DisconnectError(msg)); } self.inner.send(msg).await.map_err(Into::into) } /// Attempts to immediately send out the message to the subscribers but fails if the - /// channel is full or that the connection is closed. + /// channel is full, that the connection is closed or the subscription was not explicitly accepted. /// /// This is useful if you want to replace to old messages and keep your own buffer. /// /// This differs from [`SubscriptionSink::send`] as it will until there is capacity /// in the channel. - pub fn try_send(&mut self, msg: String) -> Result<(), SubscriptionSinkError> { - if self.id.is_some() { - todo!(); - } - + pub fn try_send(&mut self, msg: String) -> Result<(), TrySendError> { // Only possible to trigger when the connection is dropped. if self.is_closed() { - return Err(SubscriptionSinkError::Disconnected(msg)); + return Err(TrySendError::Closed(msg)); } self.inner.try_send(msg).map_err(Into::into) @@ -860,23 +859,6 @@ impl SubscriptionSink { self.inner.closed().await } - fn is_active_subscription(&self) -> bool { - match self.unsubscribe.as_ref() { - Some(unsubscribe) => unsubscribe.has_changed().is_ok(), - _ => false, - } - } - - async fn answer_subscription( - &mut self, - response: MethodResponse, - subscribe_call: oneshot::Sender, - ) -> Result<(), SubscriptionAcceptRejectError> { - let permit = self.inner.reserve().await.map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; - permit.send_raw(response.result.clone()); - subscribe_call.send(response).map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted) - } - /// ... pub fn build_message(&self, result: &T) -> Result { serde_json::to_string(&SubscriptionResponse::new( @@ -915,19 +897,7 @@ impl SubscriptionSink { /// } /// ``` /// - pub fn close(mut self, err: impl Into) -> impl Future { - if let Some((id, subscribe_call)) = self.id.take() { - // Subscription was never accepted / rejected. As such, - // we default to assuming that the params were invalid, - // because that's how the previous PendingSubscription logic - // worked. - let err = MethodResponse::error(id, ErrorObject::from(ErrorCode::InvalidParams)); - return async move { - let _ = self.answer_subscription(err, subscribe_call).await; - } - .boxed(); - } - + pub fn close(self, err: impl Into) -> impl Future { if self.is_active_subscription() { if let Some((sink, _)) = self.subscribers.lock().remove(&self.uniq_sub) { tracing::debug!("Closing subscription: {:?}", self.uniq_sub.sub_id); @@ -946,11 +916,20 @@ impl SubscriptionSink { } futures_util::future::ready(()).boxed() } + + fn is_active_subscription(&self) -> bool { + match self.unsubscribe.as_ref() { + Some(unsubscribe) => unsubscribe.has_changed().is_ok(), + _ => false, + } + } } impl Drop for SubscriptionSink { fn drop(&mut self) { - self.subscribers.lock().remove(&self.uniq_sub); + if self.is_active_subscription() { + self.subscribers.lock().remove(&self.uniq_sub); + } } } diff --git a/examples/examples/proc_macro.rs b/examples/examples/proc_macro.rs index eebedc3cbb..75603415a2 100644 --- a/examples/examples/proc_macro.rs +++ b/examples/examples/proc_macro.rs @@ -28,7 +28,7 @@ use std::net::SocketAddr; use jsonrpsee::core::{async_trait, client::Subscription, Error}; use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::{ServerBuilder, SubscriptionSink}; +use jsonrpsee::server::{PendingSubscriptionSink, ServerBuilder}; use jsonrpsee::types::SubscriptionResult; use jsonrpsee::ws_client::WsClientBuilder; @@ -64,10 +64,16 @@ impl RpcServer for RpcServerImpl { // Note that the server's subscription method must return `SubscriptionResult`. fn subscribe_storage( &self, - mut sink: SubscriptionSink, + pending: PendingSubscriptionSink, _keys: Option>, ) -> SubscriptionResult { - let _ = sink.send(&vec![[0; 32]]); + tokio::spawn(async move { + if let Ok(sink) = pending.accept().await { + let msg = sink.build_message(&1).unwrap(); + sink.send(msg).await.unwrap(); + } + }); + Ok(()) } } diff --git a/examples/examples/ws_pubsub_broadcast.rs b/examples/examples/ws_pubsub_broadcast.rs index 90cfc83904..e111217cbe 100644 --- a/examples/examples/ws_pubsub_broadcast.rs +++ b/examples/examples/ws_pubsub_broadcast.rs @@ -70,23 +70,29 @@ async fn run_server() -> anyhow::Result { std::thread::spawn(move || produce_items(tx2)); - module.register_subscription("subscribe_hello", "s_hello", "unsubscribe_hello", move |_, mut sink, _| { - let rx = BroadcastStream::new(tx.clone().subscribe()); - - tokio::spawn(async move { - match sink.pipe_from_try_stream(rx).await { - SubscriptionClosed::Success => { - sink.close(SubscriptionClosed::Success).await; - } - SubscriptionClosed::RemotePeerAborted => (), - SubscriptionClosed::Full => (), - SubscriptionClosed::Failed(err) => { - sink.close(err).await; - } - }; - }); - Ok(()) - })?; + module + .register_subscription("subscribe_hello", "s_hello", "unsubscribe_hello", move |_, pending, _| { + let mut rx = BroadcastStream::new(tx.clone().subscribe()); + + tokio::spawn(async move { + let mut sink = pending.accept().await.unwrap(); + let closed = sink.closed(); + + tokio::select! { + Some(Ok(v)) = rx.next() => { + let msg = sink.build_message(&v).unwrap(); + let _ = sink.try_send(msg); + } + _ = closed => { + return; + } + else => return, + }; + }); + + Ok(()) + }) + .unwrap(); let addr = server.local_addr()?; let handle = server.start(module)?; diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index 22a9746573..b2f1c34aeb 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -29,8 +29,10 @@ use std::time::Duration; use futures::StreamExt; use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; +use jsonrpsee::core::server::rpc_module::TrySendError; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, ServerBuilder}; +use jsonrpsee::types::ErrorObjectOwned; use jsonrpsee::ws_client::WsClientBuilder; use tokio::time::interval; use tokio_stream::wrappers::IntervalStream; @@ -67,15 +69,22 @@ async fn run_server() -> anyhow::Result { let server = ServerBuilder::default().set_buffer_size(10).build("127.0.0.1:9944").await?; let mut module = RpcModule::new(()); module - .register_subscription("sub_one_param", "sub_one_param", "unsub_one_param", |params, mut sink, _| { - let idx = params.one()?; - let item = LETTERS.chars().nth(idx); + .register_subscription("sub_one_param", "sub_one_param", "unsub_one_param", |params, pending, _| { + let params = params.into_owned(); + let fut = async move { + let idx = match params.one::() { + Ok(p) => p, + Err(e) => { + let _ = pending.reject(ErrorObjectOwned::from(e)).await; + return; + } + }; + let item = LETTERS.chars().nth(idx); - let interval = interval(Duration::from_millis(200)); - let mut stream = IntervalStream::new(interval).map(move |_| item); + let interval = interval(Duration::from_millis(200)); + let mut stream = IntervalStream::new(interval).map(move |_| item); - tokio::spawn(async move { - sink.accept().await.unwrap(); + let mut sink = pending.accept().await.unwrap(); while let Some(item) = stream.next().await { let notif = sink.build_message(&item).unwrap(); @@ -83,12 +92,14 @@ async fn run_server() -> anyhow::Result { tracing::info!("ignoring to send notif: {:?}", e); } } - }); + }; + + tokio::spawn(fut); Ok(()) }) .unwrap(); module - .register_subscription("sub_params_two", "params_two", "unsub_params_two", |params, mut sink, _| { + .register_subscription("sub_params_two", "params_two", "unsub_params_two", |params, pending, _| { let (one, two) = params.parse::<(usize, usize)>()?; let item = &LETTERS[one..two]; @@ -97,13 +108,21 @@ async fn run_server() -> anyhow::Result { let mut stream = IntervalStream::new(interval).map(move |_| item); tokio::spawn(async move { - sink.accept().await.unwrap(); + let mut sink = pending.accept().await.unwrap(); while let Some(item) = stream.next().await { let notif = sink.build_message(&item).unwrap(); - if let Err(e) = sink.try_send(notif) { - tracing::info!("ignoring to send notif: {:?}", e); - } + match sink.try_send(notif) { + Ok(_) => (), + Err(TrySendError::Closed(m)) => { + tracing::warn!("Subscription is closed; failed to send msg: {:}", m); + return; + } + Err(TrySendError::Full(m)) => { + // you could buffer the message if you want to and try re-send them. + tracing::info!("channel is full; dropping message: {:?}", m); + } + }; } }); diff --git a/jsonrpsee/Cargo.toml b/jsonrpsee/Cargo.toml index 7048beb97c..873e52645a 100644 --- a/jsonrpsee/Cargo.toml +++ b/jsonrpsee/Cargo.toml @@ -21,6 +21,7 @@ jsonrpsee-proc-macros = { path = "../proc-macros", version = "0.16.2", optional jsonrpsee-core = { path = "../core", version = "0.16.2", optional = true } jsonrpsee-types = { path = "../types", version = "0.16.2", optional = true } tracing = { version = "0.1.34", optional = true } +tokio = { version = "1.16", features = ["net", "rt-multi-thread", "macros", "time"], optional = true } [features] client-ws-transport = ["jsonrpsee-client-transport/ws", "jsonrpsee-client-transport/tls"] @@ -35,7 +36,7 @@ macros = ["jsonrpsee-proc-macros", "jsonrpsee-types", "tracing"] client = ["http-client", "ws-client", "wasm-client", "client-ws-transport", "client-web-transport", "async-client", "async-wasm-client", "client-core"] client-core = ["jsonrpsee-core/client"] -server = ["jsonrpsee-server", "server-core", "jsonrpsee-types"] +server = ["jsonrpsee-server", "server-core", "jsonrpsee-types", "tokio"] server-core = ["jsonrpsee-core/server"] full = ["client", "server", "macros"] diff --git a/jsonrpsee/src/lib.rs b/jsonrpsee/src/lib.rs index 2a0efbc21d..47ef9baad4 100644 --- a/jsonrpsee/src/lib.rs +++ b/jsonrpsee/src/lib.rs @@ -90,7 +90,8 @@ cfg_types! { } cfg_server! { - pub use jsonrpsee_core::server::rpc_module::{RpcModule, SubscriptionSink}; + pub use jsonrpsee_core::server::rpc_module::{RpcModule, SubscriptionSink, PendingSubscriptionSink}; + pub use tokio; } cfg_client_or_server! { diff --git a/proc-macros/src/render_server.rs b/proc-macros/src/render_server.rs index abb7e6dff0..b863363656 100644 --- a/proc-macros/src/render_server.rs +++ b/proc-macros/src/render_server.rs @@ -72,7 +72,7 @@ impl RpcDescription { let subscriptions = self.subscriptions.iter().map(|sub| { let docs = &sub.docs; - let subscription_sink_ty = self.jrps_server_item(quote! { SubscriptionSink }); + let subscription_sink_ty = self.jrps_server_item(quote! { PendingSubscriptionSink }); // Add `SubscriptionSink` as the second input parameter to the signature. let subscription_sink: syn::FnArg = syn::parse_quote!(subscription_sink: #subscription_sink_ty); let mut sub_sig = sub.signature.clone(); @@ -321,6 +321,7 @@ impl RpcDescription { let tracing = self.jrps_server_item(quote! { tracing }); let err = self.jrps_server_item(quote! { core::Error }); let sub_err = self.jrps_server_item(quote! { types::SubscriptionEmptyError }); + let tokio = self.jrps_server_item(quote! { tokio }); // Code to decode sequence of parameters from a JSON array. let decode_array = { @@ -330,9 +331,11 @@ impl RpcDescription { let #name: #ty = match seq.optional_next() { Ok(v) => v, Err(e) => { - #tracing::error!(concat!("Error parsing optional \"", stringify!(#name), "\" as \"", stringify!(#ty), "\": {:?}"), e); + #tracing::warn!(concat!("Error parsing optional \"", stringify!(#name), "\" as \"", stringify!(#ty), "\": {:?}"), e); let _e: #err = e.into(); - #pending.reject(_e)?; + #tokio::spawn(async move { + let _ = #pending.reject(_e).await; + }); return Err(#sub_err); } }; @@ -343,7 +346,7 @@ impl RpcDescription { let #name: #ty = match seq.optional_next() { Ok(v) => v, Err(e) => { - #tracing::error!(concat!("Error parsing optional \"", stringify!(#name), "\" as \"", stringify!(#ty), "\": {:?}"), e); + #tracing::warn!(concat!("Error parsing optional \"", stringify!(#name), "\" as \"", stringify!(#ty), "\": {:?}"), e); return Err(e.into()) } }; @@ -354,9 +357,11 @@ impl RpcDescription { let #name: #ty = match seq.next() { Ok(v) => v, Err(e) => { - #tracing::error!(concat!("Error parsing \"", stringify!(#name), "\" as \"", stringify!(#ty), "\": {:?}"), e); + #tracing::warn!(concat!("Error parsing \"", stringify!(#name), "\" as \"", stringify!(#ty), "\": {:?}"), e); let _e: #err = e.into(); - #pending.reject(_e)?; + #tokio::spawn(async move { + let _ = #pending.reject(_e).await; + }); return Err(#sub_err); } }; @@ -367,7 +372,7 @@ impl RpcDescription { let #name: #ty = match seq.next() { Ok(v) => v, Err(e) => { - #tracing::error!(concat!("Error parsing \"", stringify!(#name), "\" as \"", stringify!(#ty), "\": {:?}"), e); + #tracing::warn!(concat!("Error parsing \"", stringify!(#name), "\" as \"", stringify!(#ty), "\": {:?}"), e); return Err(e.into()) } }; @@ -433,9 +438,11 @@ impl RpcDescription { let parsed: ParamsObject<#(#types,)*> = match params.parse() { Ok(p) => p, Err(e) => { - #tracing::error!("Failed to parse JSON-RPC params as object: {}", e); + #tracing::warn!("Failed to parse JSON-RPC params as object: {}", e); let _e: #err = e.into(); - #pending.reject(_e)?; + #tokio::spawn(async move { + let _ = #pending.reject(_e).await; + }); return Err(#sub_err); } }; @@ -451,7 +458,7 @@ impl RpcDescription { } let parsed: ParamsObject<#(#types,)*> = params.parse().map_err(|e| { - #tracing::error!("Failed to parse JSON-RPC params as object: {}", e); + #tracing::warn!("Failed to parse JSON-RPC params as object: {}", e); e })?; (#(#destruct),*) diff --git a/server/src/lib.rs b/server/src/lib.rs index d661e4b83b..415452eb2b 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -43,7 +43,7 @@ mod tests; pub use future::ServerHandle; pub use jsonrpsee_core::server::host_filtering::AllowHosts; -pub use jsonrpsee_core::server::rpc_module::{RpcModule, SubscriptionSink}; +pub use jsonrpsee_core::server::rpc_module::{PendingSubscriptionSink, RpcModule, SubscriptionSink}; pub use jsonrpsee_core::{id_providers::*, traits::IdProvider}; pub use jsonrpsee_types as types; pub use server::{Builder as ServerBuilder, Server}; diff --git a/server/src/tests/helpers.rs b/server/src/tests/helpers.rs index 1330a0b819..45511bb9c6 100644 --- a/server/src/tests/helpers.rs +++ b/server/src/tests/helpers.rs @@ -82,10 +82,10 @@ pub(crate) async fn server_with_handles() -> (SocketAddr, ServerHandle) { }) .unwrap(); module - .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, mut sink, _| { - sink.accept()?; - + .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, pending, _| { tokio::spawn(async move { + let sink = pending.accept().await.unwrap(); + loop { let _ = &sink; tokio::time::sleep(std::time::Duration::from_secs(30)).await; diff --git a/server/src/tests/ws.rs b/server/src/tests/ws.rs index 7a945eedae..301b8a5973 100644 --- a/server/src/tests/ws.rs +++ b/server/src/tests/ws.rs @@ -546,17 +546,12 @@ async fn custom_subscription_id_works() { let addr = server.local_addr().unwrap(); let mut module = RpcModule::new(()); module - .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, mut sink, _| { - // There is no subscription ID prior to calling accept. - let sub_id = sink.subscription_id(); - assert!(sub_id.is_none()); - - sink.accept()?; + .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, sink, _| { + tokio::spawn(async move { + let sink = sink.accept().await.unwrap(); - let sub_id = sink.subscription_id(); - assert!(matches!(sub_id, Some(SubscriptionId::Str(id)) if id == "0xdeadbeef")); + assert!(matches!(sink.subscription_id(), SubscriptionId::Str(id) if id == "0xdeadbeef")); - tokio::spawn(async move { loop { let _ = &sink; tokio::time::sleep(std::time::Duration::from_secs(30)).await; diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 9ccb53863b..1c19d05a25 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -31,8 +31,11 @@ pub(crate) type Sender = soketto::Sender>>> pub(crate) type Receiver = soketto::Receiver>>>; pub(crate) async fn send_message(sender: &mut Sender, response: String) -> Result<(), Error> { - sender.send_text_owned(response).await?; - sender.flush().await.map_err(Into::into) + sender.send_text_owned(response.clone()).await?; + sender.flush().await?; + tracing::trace!("sent msg: {}", response); + + Ok(()) } pub(crate) async fn send_ping(sender: &mut Sender) -> Result<(), Error> { @@ -215,7 +218,7 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData let r = response.as_inner(); - //tx_log_from_str(&r.result, max_log_length); + tx_log_from_str(&r.result, max_log_length); logger.on_result(name, r.success, request_start, TransportProtocol::WebSocket); response } diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index f81176b529..a99e973887 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -35,7 +35,9 @@ use jsonrpsee::core::server::host_filtering::AllowHosts; use jsonrpsee::server::middleware::proxy_get_request::ProxyGetRequestLayer; use jsonrpsee::server::{ServerBuilder, ServerHandle}; use jsonrpsee::types::error::{ErrorObject, SUBSCRIPTION_CLOSED_WITH_ERROR}; -use jsonrpsee::RpcModule; +use jsonrpsee::types::ErrorObjectOwned; +use jsonrpsee::{PendingSubscriptionSink, RpcModule}; +use serde::Serialize; use tokio::time::interval; use tokio_stream::wrappers::IntervalStream; use tower_http::cors::CorsLayer; @@ -48,130 +50,78 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) module.register_method("say_hello", |_, _| Ok("hello")).unwrap(); module - .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, mut sink, _| { + .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, pending, _| { let interval = interval(Duration::from_millis(50)); let stream = IntervalStream::new(interval).map(move |_| &"hello from subscription"); tokio::spawn(async move { - sink.pipe_from_stream(stream).await; + pipe_from_stream(stream, pending).await; }); Ok(()) }) .unwrap(); module - .register_subscription("subscribe_foo", "subscribe_foo", "unsubscribe_foo", |_, mut sink, _| { + .register_subscription("subscribe_foo", "subscribe_foo", "unsubscribe_foo", |_, pending, _| { let interval = interval(Duration::from_millis(100)); let stream = IntervalStream::new(interval).map(move |_| 1337_usize); tokio::spawn(async move { - sink.pipe_from_stream(stream).await; + pipe_from_stream(stream, pending).await; }); Ok(()) }) .unwrap(); module - .register_subscription( - "subscribe_add_one", - "subscribe_add_one", - "unsubscribe_add_one", - |params, mut sink, _| { - let count = params.one::().map(|c| c.wrapping_add(1))?; + .register_subscription("subscribe_add_one", "subscribe_add_one", "unsubscribe_add_one", |params, pending, _| { + let params = params.into_owned(); + tokio::spawn(async move { + let count = match params.one::().map(|c| c.wrapping_add(1)) { + Ok(count) => count, + Err(e) => { + let _ = pending.reject(ErrorObjectOwned::from(e)).await; + return; + } + }; let wrapping_counter = futures::stream::iter((count..).cycle()); let interval = interval(Duration::from_millis(100)); let stream = IntervalStream::new(interval).zip(wrapping_counter).map(move |(_, c)| c); - tokio::spawn(async move { - sink.pipe_from_stream(stream).await; - }); - Ok(()) - }, - ) + pipe_from_stream(stream, pending).await; + }); + Ok(()) + }) .unwrap(); module - .register_subscription("subscribe_noop", "subscribe_noop", "unsubscribe_noop", |_, mut sink, _| { - sink.accept().unwrap(); - + .register_subscription("subscribe_noop", "subscribe_noop", "unsubscribe_noop", |_, pending, _| { tokio::spawn(async move { + let sink = pending.accept().await.unwrap(); tokio::time::sleep(Duration::from_secs(1)).await; let err = ErrorObject::owned( SUBSCRIPTION_CLOSED_WITH_ERROR, "Server closed the stream because it was lazy", None::<()>, ); - sink.close(err); + sink.close(err).await; }); Ok(()) }) .unwrap(); module - .register_subscription("subscribe_5_ints", "n", "unsubscribe_5_ints", |_, mut sink, _| { + .register_subscription("subscribe_5_ints", "n", "unsubscribe_5_ints", |_, pending, _| { tokio::spawn(async move { let interval = interval(Duration::from_millis(50)); let stream = IntervalStream::new(interval).zip(futures::stream::iter(1..=5)).map(|(_, c)| c); - - match sink.pipe_from_stream(stream).await { - SubscriptionClosed::Success => { - sink.close(SubscriptionClosed::Success); - } - _ => unreachable!(), - } + pipe_from_stream(stream, pending).await; }); Ok(()) }) .unwrap(); - module - .register_subscription("can_reuse_subscription", "n", "u_can_reuse_subscription", |_, mut sink, _| { - tokio::spawn(async move { - let stream1 = IntervalStream::new(interval(Duration::from_millis(50))) - .zip(futures::stream::iter(1..=5)) - .map(|(_, c)| c); - let stream2 = IntervalStream::new(interval(Duration::from_millis(50))) - .zip(futures::stream::iter(6..=10)) - .map(|(_, c)| c); - - let result = sink.pipe_from_stream(stream1).await; - assert!(matches!(result, SubscriptionClosed::Success)); - - match sink.pipe_from_stream(stream2).await { - SubscriptionClosed::Success => { - sink.close(SubscriptionClosed::Success); - } - _ => unreachable!(), - } - }); - Ok(()) - }) - .unwrap(); - - module - .register_subscription( - "subscribe_with_err_on_stream", - "n", - "unsubscribe_with_err_on_stream", - move |_, mut sink, _| { - let err: &'static str = "error on the stream"; - - // Create stream that produce an error which will cancel the subscription. - let stream = futures::stream::iter(vec![Ok(1_u32), Err(err), Ok(2), Ok(3)]); - tokio::spawn(async move { - match sink.pipe_from_try_stream(stream).await { - SubscriptionClosed::Failed(e) => { - sink.close(e); - } - _ => unreachable!(), - } - }); - Ok(()) - }, - ) - .unwrap(); - let addr = server.local_addr().unwrap(); let server_handle = server.start(module).unwrap(); @@ -220,12 +170,12 @@ pub async fn server_with_sleeping_subscription(tx: futures::channel::mpsc::Sende let mut module = RpcModule::new(tx); module - .register_subscription("subscribe_sleep", "n", "unsubscribe_sleep", |_, mut sink, mut tx| { + .register_subscription("subscribe_sleep", "n", "unsubscribe_sleep", |_, pending, mut tx| { tokio::spawn(async move { let interval = interval(Duration::from_secs(60 * 60)); let stream = IntervalStream::new(interval).zip(futures::stream::iter(1..=5)).map(|(_, c)| c); - sink.pipe_from_stream(stream).await; + pipe_from_stream(stream, pending).await; let send_back = std::sync::Arc::make_mut(&mut tx); send_back.send(()).await.unwrap(); }); @@ -273,3 +223,35 @@ pub fn init_logger() { .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .try_init(); } + +async fn pipe_from_stream(mut stream: S, pending: PendingSubscriptionSink) +where + S: StreamExt + Unpin, + T: Serialize, +{ + let sink = match pending.accept().await { + Ok(s) => s, + Err(_) => return, + }; + + loop { + tokio::select! { + maybe_item = stream.next() => { + let item = match maybe_item { + Some(item) => item, + None => { + let _ = sink.close(SubscriptionClosed::Success).await; + return; + } + }; + + let msg = sink.build_message(&item).unwrap(); + + if sink.send(msg).await.is_err() { + return; + } + }, + _ = sink.closed() => return, + } + } +} diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 6668a527f5..3f7ec1d8a5 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -40,7 +40,7 @@ use helpers::{ use hyper::http::HeaderValue; use jsonrpsee::core::client::{ClientT, IdKind, Subscription, SubscriptionClientT}; use jsonrpsee::core::params::{ArrayParams, BatchRequestBuilder}; -use jsonrpsee::core::server::rpc_module::SubscriptionSinkError; +use jsonrpsee::core::server::rpc_module::DisconnectError; use jsonrpsee::core::{Error, JsonValue}; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::rpc_params; @@ -433,16 +433,14 @@ async fn ws_server_should_stop_subscription_after_client_drop() { let mut module = RpcModule::new(tx); module - .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, mut sink, mut tx| { - sink.accept().unwrap(); + .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, pending, mut tx| { tokio::spawn(async move { + let sink = pending.accept().await.unwrap(); + let msg = sink.build_message(&1).unwrap(); + let close_err = loop { - match sink.send(&1_usize).await { - Ok(_) => (), - Err(SubscriptionSinkError::Send(SendError::Disconnected)) => { - break ErrorObject::borrowed(0, &"Subscription terminated successfully", None) - } - Err(e) => panic!("Unexpected error: {:?}", e), + if let Err(DisconnectError(_msg)) = sink.send(msg.clone()).await { + break ErrorObject::borrowed(0, &"Subscription terminated successfully", None); } tokio::time::sleep(Duration::from_millis(100)).await; }; @@ -571,24 +569,6 @@ async fn ws_server_cancels_subscriptions_on_reset_conn() { assert_eq!(rx_len, 10); } -#[tokio::test] -async fn ws_server_cancels_sub_stream_after_err() { - init_logger(); - - let addr = server_with_subscription().await; - let server_url = format!("ws://{}", addr); - - let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - let mut sub: Subscription = client - .subscribe("subscribe_with_err_on_stream", rpc_params![], "unsubscribe_with_err_on_stream") - .await - .unwrap(); - - assert_eq!(sub.next().await.unwrap().unwrap(), 1); - // The server closed down the subscription with the underlying error from the stream. - assert!(sub.next().await.is_none()); -} - #[tokio::test] async fn ws_server_subscribe_with_stream() { init_logger(); @@ -621,46 +601,6 @@ async fn ws_server_subscribe_with_stream() { assert!(sub1.next().await.is_none()); } -#[tokio::test] -async fn ws_server_pipe_from_stream_should_cancel_tasks_immediately() { - init_logger(); - - let (tx, rx) = mpsc::channel(1); - let server_url = format!("ws://{}", helpers::server_with_sleeping_subscription(tx).await); - - let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - let mut subs = Vec::new(); - - for _ in 0..10 { - subs.push( - client.subscribe::("subscribe_sleep", rpc_params![], "unsubscribe_sleep").await.unwrap(), - ) - } - - // This will call the `unsubscribe method`. - drop(subs); - - let rx_len = rx.take(10).fold(0, |acc, _| async move { acc + 1 }).await; - - assert_eq!(rx_len, 10); -} - -#[tokio::test] -async fn ws_server_pipe_from_stream_can_be_reused() { - init_logger(); - - let addr = server_with_subscription().await; - let client = WsClientBuilder::default().build(&format!("ws://{}", addr)).await.unwrap(); - let sub = client - .subscribe::("can_reuse_subscription", rpc_params![], "u_can_reuse_subscription") - .await - .unwrap(); - - let items = sub.fold(0, |acc, _| async move { acc + 1 }).await; - - assert_eq!(items, 10); -} - #[tokio::test] async fn ws_batch_works() { init_logger(); diff --git a/tests/tests/proc_macros.rs b/tests/tests/proc_macros.rs index 17d5b66ee7..28875e582d 100644 --- a/tests/tests/proc_macros.rs +++ b/tests/tests/proc_macros.rs @@ -45,7 +45,7 @@ mod rpc_impl { use jsonrpsee::core::{async_trait, RpcResult}; use jsonrpsee::proc_macros::rpc; use jsonrpsee::types::SubscriptionResult; - use jsonrpsee::SubscriptionSink; + use jsonrpsee::PendingSubscriptionSink; #[rpc(client, server, namespace = "foo")] pub trait Rpc { @@ -168,15 +168,24 @@ mod rpc_impl { Ok(10u16) } - fn sub(&self, mut sink: SubscriptionSink) -> SubscriptionResult { - let _ = sink.send(&"Response_A"); - let _ = sink.send(&"Response_B"); + fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { + tokio::spawn(async move { + let sink = pending.accept().await.unwrap(); + + //let _ = sink.send(&"Response_A").await; + //let _ = sink.send(&"Response_B"); + }); + Ok(()) } - fn sub_with_params(&self, mut sink: SubscriptionSink, val: u32) -> SubscriptionResult { - let _ = sink.send(&val); - let _ = sink.send(&val); + fn sub_with_params(&self, pending: PendingSubscriptionSink, val: u32) -> SubscriptionResult { + tokio::spawn(async move { + let sink = pending.accept().await.unwrap(); + + //let _ = sink.send(&"Response_A").await; + //let _ = sink.send(&"Response_B"); + }); Ok(()) } } @@ -190,8 +199,13 @@ mod rpc_impl { #[async_trait] impl OnlyGenericSubscriptionServer for RpcServerImpl { - fn sub(&self, mut sink: SubscriptionSink, _: String) -> SubscriptionResult { - let _ = sink.send(&"hello"); + fn sub(&self, pending: PendingSubscriptionSink, _: String) -> SubscriptionResult { + tokio::spawn(async move { + let sink = pending.accept().await.unwrap(); + let msg = sink.build_message(&"hello").unwrap(); + let _ = sink.send(msg).await.unwrap(); + }); + Ok(()) } } diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index efe32c2b1a..db846501a1 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -31,10 +31,10 @@ use std::time::Duration; use futures::StreamExt; use helpers::init_logger; -use jsonrpsee::core::error::{Error, SubscriptionClosed}; +use jsonrpsee::core::error::Error; use jsonrpsee::core::server::rpc_module::*; use jsonrpsee::types::error::{CallError, ErrorCode, ErrorObject, PARSE_ERROR_CODE}; -use jsonrpsee::types::{EmptyServerParams, Params}; +use jsonrpsee::types::{EmptyServerParams, ErrorObjectOwned, Params}; use serde::{Deserialize, Serialize}; use tokio::time::interval; use tokio_stream::wrappers::IntervalStream; @@ -233,18 +233,20 @@ async fn subscribing_without_server() { let mut module = RpcModule::new(()); module - .register_subscription("my_sub", "my_sub", "my_unsub", |_, mut sink, _| { + .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| { let mut stream_data = vec!['0', '1', '2']; tokio::spawn(async move { - sink.accept().await.unwrap(); + let sink = pending.accept().await.unwrap(); + while let Some(letter) = stream_data.pop() { tracing::debug!("This is your friendly subscription sending data."); - let _ = sink.send(&letter).await.unwrap(); + let msg = sink.build_message(&letter).unwrap(); + let _ = sink.send(msg).await.unwrap(); tokio::time::sleep(std::time::Duration::from_millis(500)).await; } let close = ErrorObject::borrowed(0, &"closed successfully", None); - sink.close(close.into_owned()); + let _ = sink.close(close.into_owned()).await; }); Ok(()) }) @@ -266,23 +268,21 @@ async fn close_test_subscribing_without_server() { let mut module = RpcModule::new(()); module - .register_subscription("my_sub", "my_sub", "my_unsub", |_, mut sink, _| { + .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| { tokio::spawn(async move { - sink.accept().await.unwrap(); + let sink = pending.accept().await.unwrap(); + let msg = sink.build_message(&"lo").unwrap(); // make sure to only send one item - sink.send(&"lo").await.unwrap(); + sink.send(msg.clone()).await.unwrap(); while !sink.is_closed() { tracing::debug!("[test] Sink is open, sleeping"); tokio::time::sleep(std::time::Duration::from_millis(500)).await; } - match sink.send(&"lo").await { + match sink.send(msg).await { Ok(_) => panic!("The sink should be closed"), - Err(SubscriptionSinkError::Disconnected) => { - sink.close(SubscriptionClosed::RemotePeerAborted); - } - Err(other) => panic!("Unexpected error: {:?}", other), + Err(DisconnectError(_)) => {} } }); Ok(()) @@ -317,18 +317,25 @@ async fn close_test_subscribing_without_server() { async fn subscribing_without_server_bad_params() { let mut module = RpcModule::new(()); module - .register_subscription("my_sub", "my_sub", "my_unsub", |params, mut sink, _| { - let p = match params.one::() { - Ok(p) => p, - Err(e) => { - let err: Error = e.into(); - let _ = sink.reject(err); - return Ok(()); - } + .register_subscription("my_sub", "my_sub", "my_unsub", |params, pending, _| { + let params = params.into_owned(); + let fut = async move { + let p = match params.one::() { + Ok(p) => p, + Err(e) => { + let err: ErrorObjectOwned = e.into(); + let _ = pending.reject(err).await; + return; + } + }; + + let sink = pending.accept().await.unwrap(); + let msg = sink.build_message(&p).unwrap(); + sink.send(msg).await.unwrap(); }; - sink.accept()?; - sink.send(&p).unwrap(); + tokio::spawn(fut); + Ok(()) }) .unwrap(); @@ -344,12 +351,19 @@ async fn subscribing_without_server_bad_params() { async fn subscribe_unsubscribe_without_server() { let mut module = RpcModule::new(()); module - .register_subscription("my_sub", "my_sub", "my_unsub", |_, mut sink, _| { + .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| { let interval = interval(Duration::from_millis(200)); - let stream = IntervalStream::new(interval).map(move |_| 1); + let mut stream = IntervalStream::new(interval).map(move |_| 1); tokio::spawn(async move { - sink.pipe_from_stream(stream).await; + let sink = pending.accept().await.unwrap(); + + while let Some(item) = stream.next().await { + let msg = sink.build_message(&item).unwrap(); + if sink.send(msg).await.is_ok() { + return; + } + } }); Ok(()) }) @@ -401,9 +415,12 @@ async fn empty_subscription_without_server() { async fn rejected_subscription_without_server() { let mut module = RpcModule::new(()); module - .register_subscription("my_sub", "my_sub", "my_unsub", |_, mut sink, _| { - let err = ErrorObject::borrowed(PARSE_ERROR_CODE, &"rejected", None); - sink.reject(err.into_owned())?; + .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| { + tokio::spawn(async move { + let err = ErrorObject::borrowed(PARSE_ERROR_CODE, &"rejected", None); + let _ = pending.reject(err.into_owned()).await; + }); + Ok(()) }) .unwrap(); @@ -415,42 +432,15 @@ async fn rejected_subscription_without_server() { } #[tokio::test] -async fn accepted_twice_subscription_without_server() { - let mut module = RpcModule::new(()); - module - .register_subscription("my_sub", "my_sub", "my_unsub", |_, mut sink, _| { - let res = sink.accept(); - assert!(matches!(res, Ok(_))); - - let res = sink.accept(); - assert!(matches!(res, Err(_))); - - let err = ErrorObject::borrowed(PARSE_ERROR_CODE, &"rejected", None); - let res = sink.reject(err.into_owned()); - assert!(matches!(res, Err(_))); - - Ok(()) - }) - .unwrap(); - - let _ = module.subscribe("my_sub", EmptyServerParams::new()).await.expect("Subscription should not fail"); -} - -#[tokio::test] -async fn reject_twice_subscription_without_server() { +async fn reject_works() { let mut module = RpcModule::new(()); module - .register_subscription("my_sub", "my_sub", "my_unsub", |_, mut sink, _| { - let err = ErrorObject::borrowed(PARSE_ERROR_CODE, &"rejected", None); - let res = sink.reject(err.into_owned()); - assert!(matches!(res, Ok(()))); - - let err = ErrorObject::borrowed(PARSE_ERROR_CODE, &"rejected", None); - let res = sink.reject(err.into_owned()); - assert!(matches!(res, Err(_))); - - let res = sink.accept(); - assert!(matches!(res, Err(_))); + .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| { + tokio::spawn(async move { + let err = ErrorObject::borrowed(PARSE_ERROR_CODE, &"rejected", None); + let res = pending.reject(err.into_owned()).await; + assert!(matches!(res, Ok(()))); + }); Ok(()) }) diff --git a/types/src/error.rs b/types/src/error.rs index 9644faefb3..b64f677192 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -122,6 +122,8 @@ pub enum SubscriptionAcceptRejectError { AlreadyCalled, /// The remote peer closed the connection or called the unsubscribe method. RemotePeerAborted, + /// The subscription response message was too large. + MessageTooLarge, } /// Owned variant of [`ErrorObject`]. From 125408067762ef21955a3fd8a24a290568e65515 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 17 Jan 2023 12:41:51 +0100 Subject: [PATCH 12/60] more refactoring --- core/src/server/helpers.rs | 4 ++ core/src/server/rpc_module.rs | 97 ++++++++++++++++++++--------------- tests/tests/helpers.rs | 6 ++- tests/tests/proc_macros.rs | 9 ++-- tests/tests/rpc_module.rs | 16 ------ 5 files changed, 71 insertions(+), 61 deletions(-) diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index 90dbedde73..f0c31e4da9 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -108,6 +108,10 @@ impl MethodSink { } /// Same as [`tokio::sync::mpsc::Sender::closed`]. + /// + /// # Cancel safety + /// This method is cancel safe. Once the channel is closed, + /// it stays closed forever and all future calls to closed will return immediately. pub async fn closed(&self) { self.tx.closed().await } diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 27ad58aa68..6b7bd86961 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -34,6 +34,7 @@ use crate::error::Error; use crate::id_providers::RandomIntegerIdProvider; use crate::server::helpers::MethodSink; use crate::traits::{IdProvider, ToRpcParams}; +use futures_util::future::Either; use futures_util::{future::BoxFuture, FutureExt}; use jsonrpsee_types::error::{CallError, ErrorCode, ErrorObject, ErrorObjectOwned, SubscriptionAcceptRejectError}; use jsonrpsee_types::response::{SubscriptionError, SubscriptionPayloadError}; @@ -44,7 +45,7 @@ use jsonrpsee_types::{ use parking_lot::Mutex; use rustc_hash::FxHashMap; use serde::{de::DeserializeOwned, Serialize}; -use tokio::sync::{mpsc, oneshot, watch}; +use tokio::sync::{mpsc, oneshot}; use super::helpers::{MethodResponse, MethodSinkPermit}; @@ -111,7 +112,7 @@ impl<'a> std::fmt::Debug for ConnState<'a> { } } -type Subscribers = Arc)>>>; +type Subscribers = Arc)>>>; /// Subscription message. #[derive(Debug, Clone)] @@ -669,12 +670,12 @@ impl RpcModule { let (tx, rx) = oneshot::channel(); let sink = PendingSubscriptionSink { - inner: method_sink.clone(), + inner: method_sink, method: notif_method_name, subscribers: subscribers.clone(), uniq_sub, - id: (id.clone().into_owned(), tx), - unsubscribe: None, + id: id.clone().into_owned(), + subscribe: tx, }; // The callback returns a `SubscriptionResult` for better ergonomics and is not propagated further. @@ -714,13 +715,37 @@ impl RpcModule { } } -/// Returns once the unsubscribe method has been called. -type UnsubscribeCall = Option>; +/// Represents a subscription until it is unsubscribed. +/// +// NOTE: The reason why we use `mpsc` here is because it allows `IsUnsubscribed::unsubscribed` +// to be &self instead of &mut self. +#[derive(Debug)] +struct IsUnsubscribed(mpsc::Sender<()>); + +impl IsUnsubscribed { + /// Returns true if the unsubscribe method has been invoked or the subscription has been canceled. + /// + /// This can be called multiple times as the element in the channel is never + /// removed. + fn is_unsubscribed(&self) -> bool { + self.0.is_closed() + } + + /// Wrapper over [`tokio::sync::mpsc::Sender::closed`] + /// + /// # Cancel safety + /// This method is cancel safe. Once the channel is closed, + /// it stays closed forever and all future calls to closed will return immediately. + async fn unsubscribed(&self) { + _ = self.0.closed().await; + } +} /// Represents a single subscription that is waiting to be accepted or rejected. /// /// You must either call `accept` or `reject` otherwise this does nothing. #[derive(Debug)] +#[must_use = "PendningSubscriptionSink does nothing unless `accept` or `reject` is called"] pub struct PendingSubscriptionSink { /// Sink. inner: MethodSink, @@ -732,53 +757,49 @@ pub struct PendingSubscriptionSink { uniq_sub: SubscriptionKey, /// ID of the `subscription call` (i.e. not the same as subscription id) which is used /// to reply to subscription method call and must only be used once. - id: (Id<'static>, oneshot::Sender), - /// Having some value means the subscription was accepted. - unsubscribe: UnsubscribeCall, + id: Id<'static>, + /// Sender to answer the subscribe call. + subscribe: oneshot::Sender, } impl PendingSubscriptionSink { /// Reject the subscription call with the error from [`ErrorObject`]. pub async fn reject(self, err: impl Into) -> Result<(), SubscriptionAcceptRejectError> { - let (id, subscribe_call) = self.id; - - let err = MethodResponse::error(id, err.into()); + let err = MethodResponse::error(self.id, err.into()); let permit = self.inner.reserve().await.map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; - Self::answer_subscription(permit, err, subscribe_call).await?; + Self::answer_subscription(permit, err, self.subscribe).await?; Ok(()) } /// Attempt to accept the subscription and respond the subscription method call. /// /// Fails if the connection was closed or the message was too large. - pub async fn accept(mut self) -> Result { - let (id, subscribe_call) = self.id; - - let response = MethodResponse::response(id, &self.uniq_sub.sub_id, self.inner.max_response_size() as usize); + pub async fn accept(self) -> Result { + let response = + MethodResponse::response(self.id, &self.uniq_sub.sub_id, self.inner.max_response_size() as usize); let success = response.success; let permit = self.inner.reserve().await.map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; - Self::answer_subscription(permit, response, subscribe_call).await?; + Self::answer_subscription(permit, response, self.subscribe).await?; if success { - let (tx, rx) = watch::channel(()); - self.subscribers.lock().insert(self.uniq_sub.clone(), (self.inner.clone(), tx)); - self.unsubscribe = Some(rx); + let (tx, rx) = mpsc::channel(1); + self.subscribers.lock().insert(self.uniq_sub.clone(), (self.inner.clone(), rx)); Ok(SubscriptionSink { inner: self.inner, method: self.method, subscribers: self.subscribers, uniq_sub: self.uniq_sub, - unsubscribe: self.unsubscribe, + unsubscribe: IsUnsubscribed(tx), }) } else { Err(SubscriptionAcceptRejectError::MessageTooLarge) } } - async fn answer_subscription<'a>( - permit: MethodSinkPermit<'a>, + async fn answer_subscription( + permit: MethodSinkPermit<'_>, response: MethodResponse, subscribe_call: oneshot::Sender, ) -> Result<(), SubscriptionAcceptRejectError> { @@ -798,8 +819,8 @@ pub struct SubscriptionSink { subscribers: Subscribers, /// Unique subscription. uniq_sub: SubscriptionKey, - /// Having some value means the subscription was accepted. - unsubscribe: UnsubscribeCall, + /// A future to that fires once the unsubscribe method has been called. + unsubscribe: IsUnsubscribed, } impl SubscriptionSink { @@ -850,13 +871,13 @@ impl SubscriptionSink { self.inner.is_closed() || !self.is_active_subscription() } - /// See [`MethodSink::closed`] for documentation. + /// Completes when the subscription has been closed. pub async fn closed(&self) { - if !self.is_active_subscription() { - return; + // Both are cancel-safe thus ok to use select here. + tokio::select! { + _ = self.inner.closed() => (), + _ = self.unsubscribe.unsubscribed() => (), } - - self.inner.closed().await } /// ... @@ -904,24 +925,20 @@ impl SubscriptionSink { let msg = self.build_error_message(&err.into()).expect("valid json infallible; qed"); - return async move { + return Either::Right(async move { let permit = match sink.reserve().await { Ok(permit) => permit, Err(_) => return, }; permit.send_raw(msg); - } - .boxed(); + }); } } - futures_util::future::ready(()).boxed() + Either::Left(futures_util::future::ready(())) } fn is_active_subscription(&self) -> bool { - match self.unsubscribe.as_ref() { - Some(unsubscribe) => unsubscribe.has_changed().is_ok(), - _ => false, - } + !self.unsubscribe.is_unsubscribed() } } diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index a99e973887..aa2cb68578 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -236,6 +236,10 @@ where loop { tokio::select! { + // poll the sink first. + biased; + _ = sink.closed() => return, + maybe_item = stream.next() => { let item = match maybe_item { Some(item) => item, @@ -251,7 +255,7 @@ where return; } }, - _ = sink.closed() => return, + } } } diff --git a/tests/tests/proc_macros.rs b/tests/tests/proc_macros.rs index 28875e582d..8e122298b6 100644 --- a/tests/tests/proc_macros.rs +++ b/tests/tests/proc_macros.rs @@ -172,8 +172,8 @@ mod rpc_impl { tokio::spawn(async move { let sink = pending.accept().await.unwrap(); - //let _ = sink.send(&"Response_A").await; - //let _ = sink.send(&"Response_B"); + let _ = sink.send(sink.build_message(&"Response_A").unwrap()).await; + let _ = sink.send(sink.build_message(&"Response_B").unwrap()).await; }); Ok(()) @@ -182,9 +182,10 @@ mod rpc_impl { fn sub_with_params(&self, pending: PendingSubscriptionSink, val: u32) -> SubscriptionResult { tokio::spawn(async move { let sink = pending.accept().await.unwrap(); + let msg = sink.build_message(&val).unwrap(); - //let _ = sink.send(&"Response_A").await; - //let _ = sink.send(&"Response_B"); + let _ = sink.send(msg.clone()).await; + let _ = sink.send(msg).await; }); Ok(()) } diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index db846501a1..dc5f41f574 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -395,22 +395,6 @@ async fn subscribe_unsubscribe_without_server() { futures::future::join(sub1, sub2).await; } -#[tokio::test] -async fn empty_subscription_without_server() { - let mut module = RpcModule::new(()); - module - .register_subscription("my_sub", "my_sub", "my_unsub", |_, mut _sink, _| { - // Sink was never accepted or rejected. Expected to return `InvalidParams`. - Ok(()) - }) - .unwrap(); - - let sub_err = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap_err(); - assert!( - matches!(sub_err, Error::Call(CallError::Custom(e)) if e.message().contains("Invalid params") && e.code() == ErrorCode::InvalidParams.code()) - ); -} - #[tokio::test] async fn rejected_subscription_without_server() { let mut module = RpcModule::new(()); From 72f0b7de2083b4b591650f37b43399171b52fe91 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 17 Jan 2023 13:04:01 +0100 Subject: [PATCH 13/60] add example of old APIs --- core/src/server/rpc_module.rs | 38 ++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 6b7bd86961..b4d41dbc72 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -836,7 +836,10 @@ impl SubscriptionSink { self.method } - /// Send a message back to subscribers asyncronously. + /// Send a message back to the subscribers asyncronously. + /// + /// The JSON string must be JSON-RPC subscription notification and you can use `SubscribeSink::build_message` + /// to accomplish that. /// /// Returns /// - `Ok(())` if the message could be sent. @@ -850,10 +853,25 @@ impl SubscriptionSink { self.inner.send(msg).await.map_err(Into::into) } - /// Attempts to immediately send out the message to the subscribers but fails if the + /// Similar to `SubscriptionSink::try_send` but it encodes the message as + /// a JSON-RPC subscription notification. + /// The error could either be encode error or send error. + /// + /// NOTE: if this fails you will get back the message as JSON string and if you want + /// to re-send then `SubscriptionSink::send` must be used. + // + // TODO(niklasad1): do we want this API?!. I don't but a bit more code to use the new APIs + // for users as the message needs to be serialized by the user of API. + pub async fn encode_and_send(&self, val: &T) -> Result<(), ()> { + let msg = self.build_message(val).map_err(|_| ())?; + self.send(msg).await.map_err(|_| ()) + } + + /// Attempts to immediately send out the message as JSON string to the subscribers but fails if the /// channel is full, that the connection is closed or the subscription was not explicitly accepted. /// - /// This is useful if you want to replace to old messages and keep your own buffer. + /// The JSON string must be JSON-RPC subscription notification and you can use `SubscribeSink::build_message` + /// to accomplish that. /// /// This differs from [`SubscriptionSink::send`] as it will until there is capacity /// in the channel. @@ -866,6 +884,20 @@ impl SubscriptionSink { self.inner.try_send(msg).map_err(Into::into) } + /// Similar to `SubscriptionSink::try_send` but it encodes the message as + /// a JSON-RPC subscription notification. + /// The error could either be encode error or send error. + // + /// NOTE: if this fails you will get back the message as JSON string and if you want + /// to re-send then `SubscriptionSink::send` must be used. + // + // TODO(niklasad1): do we want this API?!. I don't but a bit more code to use the new APIs + // for users as the message needs to be serialized by the user of API. + pub fn encode_and_try_send(&mut self, val: &T) -> Result<(), ()> { + let msg = self.build_message(val).map_err(|_| ())?; + self.try_send(msg).map_err(|_| ()) + } + /// Returns whether the subscription is closed. pub fn is_closed(&self) -> bool { self.inner.is_closed() || !self.is_active_subscription() From ede6f4a673e8dac1e4c4220724fc1aa2c0b88c86 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 17 Jan 2023 17:35:36 +0100 Subject: [PATCH 14/60] introduce opaque SubscriptionMessage --- core/src/server/helpers.rs | 10 +-- core/src/server/rpc_module.rs | 47 +++++++----- examples/Cargo.toml | 1 + examples/examples/ws.rs | 6 +- examples/examples/ws_pubsub_broadcast.rs | 89 +++++++++++++++++----- examples/examples/ws_pubsub_with_params.rs | 2 +- 6 files changed, 109 insertions(+), 46 deletions(-) diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index f0c31e4da9..17a974e9ce 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -33,7 +33,7 @@ use jsonrpsee_types::{Id, InvalidRequest, Response}; use serde::Serialize; use tokio::sync::mpsc::{self, Permit}; -use super::rpc_module::{DisconnectError, TrySendError}; +use super::rpc_module::{DisconnectError, SubscriptionMessage, TrySendError}; /// Bounded writer that allows writing at most `max_len` bytes. /// @@ -126,20 +126,20 @@ impl MethodSink { /// Returns the message if the send fails such that either can be thrown away or re-sent later. pub fn try_send(&mut self, msg: String) -> Result<(), TrySendError> { tx_log_from_str(&msg, self.max_log_length); - self.tx.try_send(msg) + self.tx.try_send(msg).map_err(Into::into) } /// Async send which will wait until there is space in channel buffer or that the subscription is disconnected. - pub async fn send(&self, msg: String) -> Result<(), DisconnectError> { + pub async fn send(&self, msg: String) -> Result<(), DisconnectError> { tx_log_from_str(&msg, self.max_log_length); self.tx.send(msg).await.map_err(Into::into) } /// Waits for channel capacity. Once capacity to send one message is available, it is reserved for the caller. - pub async fn reserve(&self) -> Result> { + pub async fn reserve(&self) -> Result { match self.tx.reserve().await { Ok(permit) => Ok(MethodSinkPermit { tx: permit, max_log_length: self.max_log_length }), - Err(e) => Err(e.into()), + Err(_) => Err(DisconnectError(SubscriptionMessage(String::new()))), } } } diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index b4d41dbc72..29cdc81476 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -77,11 +77,17 @@ pub type MaxResponseSize = usize; pub type RawRpcResponse = (MethodResponse, mpsc::Receiver, MethodSink); /// TrySendError -pub type TrySendError = mpsc::error::TrySendError; +#[derive(Debug)] +pub enum TrySendError { + /// The channel is closed. + Closed(SubscriptionMessage), + /// The channel is full. + Full(SubscriptionMessage), +} /// Disconnect error #[derive(Debug)] -pub struct DisconnectError(pub T); +pub struct DisconnectError(pub SubscriptionMessage); /// Helper struct to manage subscriptions. pub struct ConnState<'a> { @@ -100,9 +106,18 @@ pub enum InnerSubscriptionResult { Aborted, } -impl From> for DisconnectError { - fn from(e: mpsc::error::SendError) -> Self { - DisconnectError(e.0) +impl From> for DisconnectError { + fn from(e: mpsc::error::SendError) -> Self { + DisconnectError(SubscriptionMessage(e.0)) + } +} + +impl From> for TrySendError { + fn from(e: mpsc::error::TrySendError) -> Self { + match e { + mpsc::error::TrySendError::Closed(m) => Self::Closed(SubscriptionMessage(m)), + mpsc::error::TrySendError::Full(m) => Self::Full(SubscriptionMessage(m)), + } } } @@ -116,7 +131,7 @@ type Subscribers = Arc Result<(), DisconnectError> { + pub async fn send(&self, msg: SubscriptionMessage) -> Result<(), DisconnectError> { // Only possible to trigger when the connection is dropped. if self.is_closed() { return Err(DisconnectError(msg)); } - self.inner.send(msg).await.map_err(Into::into) + self.inner.send(msg.0).await.map_err(Into::into) } /// Similar to `SubscriptionSink::try_send` but it encodes the message as @@ -870,18 +883,16 @@ impl SubscriptionSink { /// Attempts to immediately send out the message as JSON string to the subscribers but fails if the /// channel is full, that the connection is closed or the subscription was not explicitly accepted. /// - /// The JSON string must be JSON-RPC subscription notification and you can use `SubscribeSink::build_message` - /// to accomplish that. /// /// This differs from [`SubscriptionSink::send`] as it will until there is capacity /// in the channel. - pub fn try_send(&mut self, msg: String) -> Result<(), TrySendError> { + pub fn try_send(&mut self, msg: SubscriptionMessage) -> Result<(), TrySendError> { // Only possible to trigger when the connection is dropped. if self.is_closed() { return Err(TrySendError::Closed(msg)); } - self.inner.try_send(msg).map_err(Into::into) + self.inner.try_send(msg.0).map_err(Into::into) } /// Similar to `SubscriptionSink::try_send` but it encodes the message as @@ -913,20 +924,22 @@ impl SubscriptionSink { } /// ... - pub fn build_message(&self, result: &T) -> Result { + pub fn build_message(&self, result: &T) -> Result { serde_json::to_string(&SubscriptionResponse::new( self.method.into(), SubscriptionPayload { subscription: self.uniq_sub.sub_id.clone(), result }, )) + .map(|json| SubscriptionMessage(json)) .map_err(Into::into) } /// ... - pub fn build_error_message(&self, error: &T) -> Result { + pub fn build_error_message(&self, error: &T) -> Result { serde_json::to_string(&SubscriptionError::new( self.method.into(), SubscriptionPayloadError { subscription: self.uniq_sub.sub_id.clone(), error }, )) + .map(|json| SubscriptionMessage(json)) .map_err(Into::into) } @@ -962,7 +975,7 @@ impl SubscriptionSink { Ok(permit) => permit, Err(_) => return, }; - permit.send_raw(msg); + permit.send_raw(msg.0); }); } } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index c0b0c64901..d138b4d840 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -19,3 +19,4 @@ tower-http = { version = "0.3.4", features = ["full"] } tower = { version = "0.4.13", features = ["full"] } hyper = "0.14.20" console-subscriber = "0.1.8" +bounded-vec-deque = "0.1.1" \ No newline at end of file diff --git a/examples/examples/ws.rs b/examples/examples/ws.rs index 5e77b22e45..dc119c1c04 100644 --- a/examples/examples/ws.rs +++ b/examples/examples/ws.rs @@ -40,13 +40,11 @@ async fn main() -> anyhow::Result<()> { tracing_subscriber::FmtSubscriber::builder().with_env_filter(filter).finish().try_init()?; let addr = run_server().await?; - /*let url = format!("ws://{}", addr); + let url = format!("ws://{}", addr); let client = WsClientBuilder::default().build(&url).await?; let response: String = client.request("say_hello", rpc_params![]).await?; - tracing::info!("response: {:?}", response);*/ - - futures::future::pending::<()>().await; + tracing::info!("response: {:?}", response); Ok(()) } diff --git a/examples/examples/ws_pubsub_broadcast.rs b/examples/examples/ws_pubsub_broadcast.rs index e111217cbe..6af1b0696f 100644 --- a/examples/examples/ws_pubsub_broadcast.rs +++ b/examples/examples/ws_pubsub_broadcast.rs @@ -28,14 +28,19 @@ use std::net::SocketAddr; +use anyhow::anyhow; use futures::future; use futures::StreamExt; use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; +use jsonrpsee::core::server::rpc_module::TrySendError; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, ServerBuilder}; use jsonrpsee::ws_client::WsClientBuilder; +use jsonrpsee::PendingSubscriptionSink; use tokio::sync::broadcast; +use tokio::time::interval; use tokio_stream::wrappers::BroadcastStream; +use tokio_stream::wrappers::IntervalStream; const NUM_SUBSCRIPTION_RESPONSES: usize = 5; @@ -63,7 +68,8 @@ async fn main() -> anyhow::Result<()> { } async fn run_server() -> anyhow::Result { - let server = ServerBuilder::default().build("127.0.0.1:0").await?; + // let's configure the server only hold 5 messages in memory. + let server = ServerBuilder::default().set_buffer_size(5).build("127.0.0.1:0").await?; let mut module = RpcModule::new(()); let (tx, _rx) = broadcast::channel(16); let tx2 = tx.clone(); @@ -72,23 +78,8 @@ async fn run_server() -> anyhow::Result { module .register_subscription("subscribe_hello", "s_hello", "unsubscribe_hello", move |_, pending, _| { - let mut rx = BroadcastStream::new(tx.clone().subscribe()); - - tokio::spawn(async move { - let mut sink = pending.accept().await.unwrap(); - let closed = sink.closed(); - - tokio::select! { - Some(Ok(v)) = rx.next() => { - let msg = sink.build_message(&v).unwrap(); - let _ = sink.try_send(msg); - } - _ = closed => { - return; - } - else => return, - }; - }); + let stream = BroadcastStream::new(tx.clone().subscribe()); + tokio::spawn(pipe_from_stream_with_bounded_buffer(pending, stream)); Ok(()) }) @@ -103,10 +94,70 @@ async fn run_server() -> anyhow::Result { Ok(addr) } +async fn pipe_from_stream_with_bounded_buffer( + pending: PendingSubscriptionSink, + mut stream: BroadcastStream, +) -> anyhow::Result<()> { + let mut sink = pending.accept().await.map_err(|e| anyhow!("{:?}", e))?; + let mut bounded_buffer = bounded_vec_deque::BoundedVecDeque::new(10); + + // This is not recommended approach it would be better to have a queue that returns a future once + // a element is ready to consumed in the bounded_buffer. This might waste CPU with the timeouts + // as it's no guarantee that buffer has elements. + let mut timeout = IntervalStream::new(interval(std::time::Duration::from_millis(100))); + + loop { + tokio::select! { + // subscription was closed, no point of reading more messages from the stream. + _ = sink.closed() => { + return Ok(()); + } + // stream yielded a new element, try to send it. + Some(Ok(v)) = stream.next() => { + let msg = sink.build_message(&v).expect("usize serialize is infalliable; qed"); + match sink.try_send(msg) { + // message was sent successfully. + Ok(()) => (), + // the connection is closed. + Err(TrySendError::Closed(_msg_unset_dont_care)) => { + tracing::warn!("The connection closed; closing subscription"); + return Ok(()); + } + // the channel was full let's buffer the ten most recent messages. + Err(TrySendError::Full(unsent_msg)) => { + if let Some(msg) = bounded_buffer.push_back(unsent_msg) { + tracing::warn!("Dropping the oldest message: {:?}", msg); + } + } + }; + } + // try to pop to oldest element for our bounded buffer and send it. + _ = timeout.next() => { + if let Some(msg) = bounded_buffer.pop_front() { + match sink.try_send(msg) { + // message was sent successfully. + Ok(()) => (), + // the connection is closed. + Err(TrySendError::Closed(_msg_unset_dont_care)) => { + tracing::warn!("The connection closed; closing subscription"); + return Ok(()); + } + // the channel was full, let's insert back the pop:ed element. + Err(TrySendError::Full(unsent_msg)) => { + bounded_buffer.push_front(unsent_msg).expect("pop:ed an element must be space in; qed"); + } + }; + } + } + else => return Ok(()), + } + } +} + // Naive example that broadcasts the produced values to all active subscribers. fn produce_items(tx: broadcast::Sender) { for c in 1..=100 { - std::thread::sleep(std::time::Duration::from_secs(1)); + std::thread::sleep(std::time::Duration::from_millis(1)); // This might fail if no receivers are alive, could occur if no subscriptions are active... // Also be aware that this will succeed when at least one receiver is alive diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index b2f1c34aeb..2559ec199f 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -115,7 +115,7 @@ async fn run_server() -> anyhow::Result { match sink.try_send(notif) { Ok(_) => (), Err(TrySendError::Closed(m)) => { - tracing::warn!("Subscription is closed; failed to send msg: {:}", m); + tracing::warn!("Subscription is closed; failed to send msg: {:?}", m); return; } Err(TrySendError::Full(m)) => { From 64e7da6d629bafd4c084c2e6c7816df0446b8e89 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 25 Jan 2023 13:05:49 +0100 Subject: [PATCH 15/60] feat: make subscription callbacks async --- benches/helpers.rs | 16 +-- core/src/server/rpc_module.rs | 16 +-- examples/examples/proc_macro.rs | 15 ++- examples/examples/ws_pubsub_broadcast.rs | 15 +-- examples/examples/ws_pubsub_with_params.rs | 76 +++++++------ proc-macros/src/render_server.rs | 4 +- proc-macros/src/rpc_macro.rs | 32 ++++-- server/src/tests/helpers.rs | 17 ++- server/src/tests/ws.rs | 24 ++--- server/src/transport/ws.rs | 2 + tests/tests/helpers.rs | 78 +++++++------- tests/tests/integration_tests.rs | 35 +++++- tests/tests/proc_macros.rs | 41 ++++--- tests/tests/rpc_module.rs | 118 +++++++++------------ 14 files changed, 254 insertions(+), 235 deletions(-) diff --git a/benches/helpers.rs b/benches/helpers.rs index fa8b7602a8..d172c801e0 100644 --- a/benches/helpers.rs +++ b/benches/helpers.rs @@ -146,15 +146,19 @@ pub async fn ws_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee::se let mut module = gen_rpc_module(); module - .register_subscription(SUB_METHOD_NAME, SUB_METHOD_NAME, UNSUB_METHOD_NAME, |_params, pending, _ctx| { - let x = "Hello"; - tokio::spawn(async move { + .register_subscription( + SUB_METHOD_NAME, + SUB_METHOD_NAME, + UNSUB_METHOD_NAME, + |_params, pending, _ctx| async move { + let x = "Hello"; + let sink = pending.accept().await.unwrap(); let msg = sink.build_message(&x).unwrap(); sink.send(msg).await.unwrap(); - }); - Ok(()) - }) + Ok(()) + }, + ) .unwrap(); let addr = format!("ws://{}", server.local_addr().unwrap()); diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 29cdc81476..135f96aa83 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -617,7 +617,7 @@ impl RpcModule { /// Ok(()) /// }); /// ``` - pub fn register_subscription( + pub fn register_subscription( &mut self, subscribe_method_name: &'static str, notif_method_name: &'static str, @@ -626,7 +626,8 @@ impl RpcModule { ) -> Result<&mut MethodCallback, Error> where Context: Send + Sync + 'static, - F: Fn(Params, PendingSubscriptionSink, Arc) -> SubscriptionResult + Send + Sync + 'static, + F: (Fn(Params<'static>, PendingSubscriptionSink, Arc) -> Fut) + Send + Sync + Clone + 'static, + Fut: Future + Send + 'static, { if subscribe_method_name == unsubscribe_method_name { return Err(Error::SubscriptionNameConflict(subscribe_method_name.into())); @@ -693,10 +694,13 @@ impl RpcModule { subscribe: tx, }; - // The callback returns a `SubscriptionResult` for better ergonomics and is not propagated further. - if callback(params, sink, ctx.clone()).is_err() { - tracing::warn!("Subscribe call `{}` failed", subscribe_method_name); - } + let call_fut = callback(params.into_owned(), sink, ctx.clone()); + + tokio::spawn(async move { + if let Err(e) = call_fut.await { + tracing::warn!("Subscribe call `{subscribe_method_name}` canceled because `{e:?}` failed"); + } + }); let id = id.clone().into_owned(); diff --git a/examples/examples/proc_macro.rs b/examples/examples/proc_macro.rs index 75603415a2..43b7ec73a7 100644 --- a/examples/examples/proc_macro.rs +++ b/examples/examples/proc_macro.rs @@ -46,7 +46,7 @@ where /// Subscription that takes a `StorageKey` as input and produces a `Vec`. #[subscription(name = "subscribeStorage" => "override", item = Vec)] - fn subscribe_storage(&self, keys: Option>); + async fn subscribe_storage(&self, keys: Option>) -> SubscriptionResult; } pub struct RpcServerImpl; @@ -62,17 +62,14 @@ impl RpcServer for RpcServerImpl { } // Note that the server's subscription method must return `SubscriptionResult`. - fn subscribe_storage( + async fn subscribe_storage( &self, pending: PendingSubscriptionSink, _keys: Option>, ) -> SubscriptionResult { - tokio::spawn(async move { - if let Ok(sink) = pending.accept().await { - let msg = sink.build_message(&1).unwrap(); - sink.send(msg).await.unwrap(); - } - }); + let sink = pending.accept().await?; + let msg = sink.build_message(&vec![[0; 32]]).unwrap(); + sink.send(msg).await.unwrap(); Ok(()) } @@ -95,6 +92,8 @@ async fn main() -> anyhow::Result<()> { RpcClient::::subscribe_storage(&client, None).await.unwrap(); assert_eq!(Some(vec![[0; 32]]), sub.next().await.transpose().unwrap()); + sub.unsubscribe().await.unwrap(); + Ok(()) } diff --git a/examples/examples/ws_pubsub_broadcast.rs b/examples/examples/ws_pubsub_broadcast.rs index 6af1b0696f..9757ef19a0 100644 --- a/examples/examples/ws_pubsub_broadcast.rs +++ b/examples/examples/ws_pubsub_broadcast.rs @@ -70,16 +70,17 @@ async fn main() -> anyhow::Result<()> { async fn run_server() -> anyhow::Result { // let's configure the server only hold 5 messages in memory. let server = ServerBuilder::default().set_buffer_size(5).build("127.0.0.1:0").await?; - let mut module = RpcModule::new(()); - let (tx, _rx) = broadcast::channel(16); - let tx2 = tx.clone(); + let (tx, _rx) = broadcast::channel::(16); - std::thread::spawn(move || produce_items(tx2)); + let mut module = RpcModule::new(tx.clone()); + + std::thread::spawn(move || produce_items(tx)); module - .register_subscription("subscribe_hello", "s_hello", "unsubscribe_hello", move |_, pending, _| { - let stream = BroadcastStream::new(tx.clone().subscribe()); - tokio::spawn(pipe_from_stream_with_bounded_buffer(pending, stream)); + .register_subscription("subscribe_hello", "s_hello", "unsubscribe_hello", |_, pending, tx| async move { + let rx = tx.subscribe(); + let stream = BroadcastStream::new(rx); + pipe_from_stream_with_bounded_buffer(pending, stream).await?; Ok(()) }) diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index 2559ec199f..7d9d259bdd 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -69,62 +69,58 @@ async fn run_server() -> anyhow::Result { let server = ServerBuilder::default().set_buffer_size(10).build("127.0.0.1:9944").await?; let mut module = RpcModule::new(()); module - .register_subscription("sub_one_param", "sub_one_param", "unsub_one_param", |params, pending, _| { + .register_subscription("sub_one_param", "sub_one_param", "unsub_one_param", |params, pending, _| async move { let params = params.into_owned(); - let fut = async move { - let idx = match params.one::() { - Ok(p) => p, - Err(e) => { - let _ = pending.reject(ErrorObjectOwned::from(e)).await; - return; - } - }; - let item = LETTERS.chars().nth(idx); - let interval = interval(Duration::from_millis(200)); - let mut stream = IntervalStream::new(interval).map(move |_| item); + let idx = match params.one::() { + Ok(p) => p, + Err(e) => { + let _ = pending.reject(ErrorObjectOwned::from(e)).await; + return Ok(()); + } + }; + let item = LETTERS.chars().nth(idx); - let mut sink = pending.accept().await.unwrap(); + let interval = interval(Duration::from_millis(200)); + let mut stream = IntervalStream::new(interval).map(move |_| item); - while let Some(item) = stream.next().await { - let notif = sink.build_message(&item).unwrap(); - if let Err(e) = sink.try_send(notif) { - tracing::info!("ignoring to send notif: {:?}", e); - } + let mut sink = pending.accept().await.unwrap(); + + while let Some(item) = stream.next().await { + let notif = sink.build_message(&item).unwrap(); + if let Err(e) = sink.try_send(notif) { + tracing::info!("ignoring to send notif: {:?}", e); } - }; + } - tokio::spawn(fut); Ok(()) }) .unwrap(); module - .register_subscription("sub_params_two", "params_two", "unsub_params_two", |params, pending, _| { - let (one, two) = params.parse::<(usize, usize)>()?; + .register_subscription("sub_params_two", "params_two", "unsub_params_two", |params, pending, _| async move { + let (one, two) = params.parse::<(usize, usize)>().unwrap(); let item = &LETTERS[one..two]; let interval = interval(Duration::from_millis(200)); let mut stream = IntervalStream::new(interval).map(move |_| item); - tokio::spawn(async move { - let mut sink = pending.accept().await.unwrap(); - - while let Some(item) = stream.next().await { - let notif = sink.build_message(&item).unwrap(); - match sink.try_send(notif) { - Ok(_) => (), - Err(TrySendError::Closed(m)) => { - tracing::warn!("Subscription is closed; failed to send msg: {:?}", m); - return; - } - Err(TrySendError::Full(m)) => { - // you could buffer the message if you want to and try re-send them. - tracing::info!("channel is full; dropping message: {:?}", m); - } - }; - } - }); + let mut sink = pending.accept().await.unwrap(); + + while let Some(item) = stream.next().await { + let notif = sink.build_message(&item).unwrap(); + match sink.try_send(notif) { + Ok(_) => (), + Err(TrySendError::Closed(m)) => { + tracing::warn!("Subscription is closed; failed to send msg: {:?}", m); + return Ok(()); + } + Err(TrySendError::Full(m)) => { + // you could buffer the message if you want to and try re-send them. + tracing::info!("channel is full; dropping message: {:?}", m); + } + }; + } Ok(()) }) diff --git a/proc-macros/src/render_server.rs b/proc-macros/src/render_server.rs index b863363656..387cc13882 100644 --- a/proc-macros/src/render_server.rs +++ b/proc-macros/src/render_server.rs @@ -216,9 +216,9 @@ impl RpcDescription { let resources = handle_resource_limits(&sub.resources); handle_register_result(quote! { - rpc.register_subscription(#rpc_sub_name, #rpc_notif_name, #rpc_unsub_name, |params, mut subscription_sink, context| { + rpc.register_subscription(#rpc_sub_name, #rpc_notif_name, #rpc_unsub_name, |params, mut subscription_sink, context| async move { #parsing - context.as_ref().#rust_method_name(subscription_sink, #params_seq) + context.as_ref().#rust_method_name(subscription_sink, #params_seq).await }) #resources }) diff --git a/proc-macros/src/rpc_macro.rs b/proc-macros/src/rpc_macro.rs index 36d15ed64b..6dfa086c90 100644 --- a/proc-macros/src/rpc_macro.rs +++ b/proc-macros/src/rpc_macro.rs @@ -295,15 +295,29 @@ impl RpcDescription { )); } - if !matches!(method.sig.output, syn::ReturnType::Default) { - return Err(syn::Error::new_spanned( - method, - "Subscription methods must not return anything; the error must send via subscription via either `SubscriptionSink::reject` or `SubscriptionSink::close`", - )); - } - - if method.sig.asyncness.is_some() { - return Err(syn::Error::new_spanned(method, "Subscription methods must not be `async`")); + match method.sig.output.clone() { + syn::ReturnType::Type(_, ty) => { + if let syn::Type::Path(syn::TypePath { path, .. }) = *ty { + if let Some(ident) = path.get_ident() { + if ident != "SubscriptionResult" && ident != "Result" { + return Err(syn::Error::new_spanned( + method, + "Subscription methods must return `SubscriptionResult` or `Result`", + )); + } + } + } + } + _ => { + return Err(syn::Error::new_spanned( + method, + "Subscription methods must return `SubscriptionResult`", + )); + } + }; + + if method.sig.asyncness.is_none() { + return Err(syn::Error::new_spanned(method, "Subscription methods must be `async`")); } let sub_data = RpcSubscription::from_item(attr.clone(), method.clone())?; diff --git a/server/src/tests/helpers.rs b/server/src/tests/helpers.rs index 45511bb9c6..566c36dd96 100644 --- a/server/src/tests/helpers.rs +++ b/server/src/tests/helpers.rs @@ -82,16 +82,13 @@ pub(crate) async fn server_with_handles() -> (SocketAddr, ServerHandle) { }) .unwrap(); module - .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, pending, _| { - tokio::spawn(async move { - let sink = pending.accept().await.unwrap(); - - loop { - let _ = &sink; - tokio::time::sleep(std::time::Duration::from_secs(30)).await; - } - }); - Ok(()) + .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, pending, _| async move { + let sink = pending.accept().await.unwrap(); + + loop { + let _ = &sink; + tokio::time::sleep(std::time::Duration::from_secs(30)).await; + } }) .unwrap(); module.register_method("notif", |_, _| Ok("")).unwrap(); diff --git a/server/src/tests/ws.rs b/server/src/tests/ws.rs index 301b8a5973..c48d8a28bb 100644 --- a/server/src/tests/ws.rs +++ b/server/src/tests/ws.rs @@ -402,10 +402,10 @@ async fn register_methods_works() { assert!(module.register_method("say_hello", |_, _| Ok("lo")).is_ok()); assert!(module.register_method("say_hello", |_, _| Ok("lo")).is_err()); assert!(module - .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, _, _| { Ok(()) }) + .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, _, _| async { Ok(()) }) .is_ok()); assert!(module - .register_subscription("subscribe_hello_again", "subscribe_hello_again", "unsubscribe_hello", |_, _, _| { + .register_subscription("subscribe_hello_again", "subscribe_hello_again", "unsubscribe_hello", |_, _, _| async { Ok(()) }) .is_err()); @@ -419,7 +419,8 @@ async fn register_methods_works() { async fn register_same_subscribe_unsubscribe_is_err() { let mut module = RpcModule::new(()); assert!(matches!( - module.register_subscription("subscribe_hello", "subscribe_hello", "subscribe_hello", |_, _, _| { Ok(()) }), + module + .register_subscription("subscribe_hello", "subscribe_hello", "subscribe_hello", |_, _, _| async { Ok(()) }), Err(Error::SubscriptionNameConflict(_)) )); } @@ -546,18 +547,15 @@ async fn custom_subscription_id_works() { let addr = server.local_addr().unwrap(); let mut module = RpcModule::new(()); module - .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, sink, _| { - tokio::spawn(async move { - let sink = sink.accept().await.unwrap(); + .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, sink, _| async { + let sink = sink.accept().await.unwrap(); - assert!(matches!(sink.subscription_id(), SubscriptionId::Str(id) if id == "0xdeadbeef")); + assert!(matches!(sink.subscription_id(), SubscriptionId::Str(id) if id == "0xdeadbeef")); - loop { - let _ = &sink; - tokio::time::sleep(std::time::Duration::from_secs(30)).await; - } - }); - Ok(()) + loop { + let _ = &sink; + tokio::time::sleep(std::time::Duration::from_secs(30)).await; + } }) .unwrap(); let _handle = server.start(module).unwrap(); diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 1c19d05a25..3ea80beaf2 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -31,6 +31,7 @@ pub(crate) type Sender = soketto::Sender>>> pub(crate) type Receiver = soketto::Receiver>>>; pub(crate) async fn send_message(sender: &mut Sender, response: String) -> Result<(), Error> { + tracing::trace!("attempting to send: {}", response); sender.send_text_owned(response.clone()).await?; sender.flush().await?; tracing::trace!("sent msg: {}", response); @@ -204,6 +205,7 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData let conn_state = ConnState { conn_id, id_provider }; let response = callback(id.clone(), params, sink.clone(), conn_state).await; + // TODO(niklasad1): investigate why we need that. MethodResult::JustLogger(response) } MethodKind::Unsubscription(callback) => { diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index aa2cb68578..deb142caf6 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -50,38 +50,38 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) module.register_method("say_hello", |_, _| Ok("hello")).unwrap(); module - .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, pending, _| { + .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, pending, _| async { let interval = interval(Duration::from_millis(50)); let stream = IntervalStream::new(interval).map(move |_| &"hello from subscription"); - tokio::spawn(async move { - pipe_from_stream(stream, pending).await; - }); + pipe_from_stream(stream, pending).await; + Ok(()) }) .unwrap(); module - .register_subscription("subscribe_foo", "subscribe_foo", "unsubscribe_foo", |_, pending, _| { + .register_subscription("subscribe_foo", "subscribe_foo", "unsubscribe_foo", |_, pending, _| async { let interval = interval(Duration::from_millis(100)); let stream = IntervalStream::new(interval).map(move |_| 1337_usize); - tokio::spawn(async move { - pipe_from_stream(stream, pending).await; - }); + pipe_from_stream(stream, pending).await; + Ok(()) }) .unwrap(); module - .register_subscription("subscribe_add_one", "subscribe_add_one", "unsubscribe_add_one", |params, pending, _| { - let params = params.into_owned(); - tokio::spawn(async move { + .register_subscription( + "subscribe_add_one", + "subscribe_add_one", + "unsubscribe_add_one", + |params, pending, _| async move { let count = match params.one::().map(|c| c.wrapping_add(1)) { Ok(count) => count, Err(e) => { let _ = pending.reject(ErrorObjectOwned::from(e)).await; - return; + return Ok(()); } }; @@ -90,34 +90,33 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) let stream = IntervalStream::new(interval).zip(wrapping_counter).map(move |(_, c)| c); pipe_from_stream(stream, pending).await; - }); - Ok(()) - }) + + Ok(()) + }, + ) .unwrap(); module - .register_subscription("subscribe_noop", "subscribe_noop", "unsubscribe_noop", |_, pending, _| { - tokio::spawn(async move { - let sink = pending.accept().await.unwrap(); - tokio::time::sleep(Duration::from_secs(1)).await; - let err = ErrorObject::owned( - SUBSCRIPTION_CLOSED_WITH_ERROR, - "Server closed the stream because it was lazy", - None::<()>, - ); - sink.close(err).await; - }); + .register_subscription("subscribe_noop", "subscribe_noop", "unsubscribe_noop", |_, pending, _| async { + let sink = pending.accept().await.unwrap(); + tokio::time::sleep(Duration::from_secs(1)).await; + let err = ErrorObject::owned( + SUBSCRIPTION_CLOSED_WITH_ERROR, + "Server closed the stream because it was lazy", + None::<()>, + ); + sink.close(err).await; + Ok(()) }) .unwrap(); module - .register_subscription("subscribe_5_ints", "n", "unsubscribe_5_ints", |_, pending, _| { - tokio::spawn(async move { - let interval = interval(Duration::from_millis(50)); - let stream = IntervalStream::new(interval).zip(futures::stream::iter(1..=5)).map(|(_, c)| c); - pipe_from_stream(stream, pending).await; - }); + .register_subscription("subscribe_5_ints", "n", "unsubscribe_5_ints", |_, pending, _| async move { + let interval = interval(Duration::from_millis(50)); + let stream = IntervalStream::new(interval).zip(futures::stream::iter(1..=5)).map(|(_, c)| c); + pipe_from_stream(stream, pending).await; + Ok(()) }) .unwrap(); @@ -170,15 +169,14 @@ pub async fn server_with_sleeping_subscription(tx: futures::channel::mpsc::Sende let mut module = RpcModule::new(tx); module - .register_subscription("subscribe_sleep", "n", "unsubscribe_sleep", |_, pending, mut tx| { - tokio::spawn(async move { - let interval = interval(Duration::from_secs(60 * 60)); - let stream = IntervalStream::new(interval).zip(futures::stream::iter(1..=5)).map(|(_, c)| c); + .register_subscription("subscribe_sleep", "n", "unsubscribe_sleep", |_, pending, mut tx| async move { + let interval = interval(Duration::from_secs(60 * 60)); + let stream = IntervalStream::new(interval).zip(futures::stream::iter(1..=5)).map(|(_, c)| c); + + pipe_from_stream(stream, pending).await; + let send_back = std::sync::Arc::make_mut(&mut tx); + send_back.send(()).await.unwrap(); - pipe_from_stream(stream, pending).await; - let send_back = std::sync::Arc::make_mut(&mut tx); - send_back.send(()).await.unwrap(); - }); Ok(()) }) .unwrap(); diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 3f7ec1d8a5..7f8012df35 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -433,8 +433,11 @@ async fn ws_server_should_stop_subscription_after_client_drop() { let mut module = RpcModule::new(tx); module - .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, pending, mut tx| { - tokio::spawn(async move { + .register_subscription( + "subscribe_hello", + "subscribe_hello", + "unsubscribe_hello", + |_, pending, mut tx| async move { let sink = pending.accept().await.unwrap(); let msg = sink.build_message(&1).unwrap(); @@ -446,9 +449,10 @@ async fn ws_server_should_stop_subscription_after_client_drop() { }; let send_back = Arc::make_mut(&mut tx); send_back.feed(close_err).await.unwrap(); - }); - Ok(()) - }) + + Ok(()) + }, + ) .unwrap(); let _handle = server.start(module).unwrap(); @@ -468,6 +472,27 @@ async fn ws_server_should_stop_subscription_after_client_drop() { assert_eq!(close_err, ErrorObject::borrowed(0, &"Subscription terminated successfully", None)); } +#[tokio::test] +async fn ws_server_stop_subscription_when_dropped() { + use jsonrpsee::{server::ServerBuilder, RpcModule}; + + init_logger(); + + let server = ServerBuilder::default().build("127.0.0.1:0").await.unwrap(); + let server_url = format!("ws://{}", server.local_addr().unwrap()); + + let mut module = RpcModule::new(()); + + module + .register_subscription("subscribe_nop", "h", "unsubscribe_nop", |_params, _pending, _ctx| async { Ok(()) }) + .unwrap(); + + let _handle = server.start(module).unwrap(); + let client = WsClientBuilder::default().build(&server_url).await.unwrap(); + + assert!(client.subscribe::("subscribe_nop", rpc_params![], "unsubscribe_nop").await.is_err()); +} + #[tokio::test] async fn ws_server_notify_client_on_disconnect() { use futures::channel::oneshot; diff --git a/tests/tests/proc_macros.rs b/tests/tests/proc_macros.rs index 8e122298b6..c5fea932ba 100644 --- a/tests/tests/proc_macros.rs +++ b/tests/tests/proc_macros.rs @@ -56,10 +56,10 @@ mod rpc_impl { fn sync_method(&self) -> RpcResult; #[subscription(name = "sub", unsubscribe = "unsub", item = String)] - fn sub(&self); + async fn sub(&self) -> SubscriptionResult; #[subscription(name = "echo", unsubscribe = "unsubscribe_echo", aliases = ["alias_echo"], item = u32)] - fn sub_with_params(&self, val: u32); + async fn sub_with_params(&self, val: u32) -> SubscriptionResult; #[method(name = "params")] fn params(&self, a: u8, b: &str) -> RpcResult { @@ -116,7 +116,7 @@ mod rpc_impl { /// All head subscription #[subscription(name = "subscribeAllHeads", item = Header)] - fn subscribe_all_heads(&self, hash: Hash); + async fn subscribe_all_heads(&self, hash: Hash) -> SubscriptionResult; } /// Trait to ensure that the trait bounds are correct. @@ -131,7 +131,7 @@ mod rpc_impl { pub trait OnlyGenericSubscription { /// Get header of a relay chain block. #[subscription(name = "sub", unsubscribe = "unsub", item = Vec)] - fn sub(&self, hash: Input); + async fn sub(&self, hash: Input) -> SubscriptionResult; } /// Trait to ensure that the trait bounds are correct. @@ -168,25 +168,22 @@ mod rpc_impl { Ok(10u16) } - fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { - tokio::spawn(async move { - let sink = pending.accept().await.unwrap(); + async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { + let sink = pending.accept().await.unwrap(); - let _ = sink.send(sink.build_message(&"Response_A").unwrap()).await; - let _ = sink.send(sink.build_message(&"Response_B").unwrap()).await; - }); + let _ = sink.send(sink.build_message(&"Response_A").unwrap()).await; + let _ = sink.send(sink.build_message(&"Response_B").unwrap()).await; Ok(()) } - fn sub_with_params(&self, pending: PendingSubscriptionSink, val: u32) -> SubscriptionResult { - tokio::spawn(async move { - let sink = pending.accept().await.unwrap(); - let msg = sink.build_message(&val).unwrap(); + async fn sub_with_params(&self, pending: PendingSubscriptionSink, val: u32) -> SubscriptionResult { + let sink = pending.accept().await.unwrap(); + let msg = sink.build_message(&val).unwrap(); + + let _ = sink.send(msg.clone()).await; + let _ = sink.send(msg).await; - let _ = sink.send(msg.clone()).await; - let _ = sink.send(msg).await; - }); Ok(()) } } @@ -200,12 +197,10 @@ mod rpc_impl { #[async_trait] impl OnlyGenericSubscriptionServer for RpcServerImpl { - fn sub(&self, pending: PendingSubscriptionSink, _: String) -> SubscriptionResult { - tokio::spawn(async move { - let sink = pending.accept().await.unwrap(); - let msg = sink.build_message(&"hello").unwrap(); - let _ = sink.send(msg).await.unwrap(); - }); + async fn sub(&self, pending: PendingSubscriptionSink, _: String) -> SubscriptionResult { + let sink = pending.accept().await.unwrap(); + let msg = sink.build_message(&"hello").unwrap(); + let _ = sink.send(msg).await.unwrap(); Ok(()) } diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index dc5f41f574..62dad9a021 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -34,7 +34,7 @@ use helpers::init_logger; use jsonrpsee::core::error::Error; use jsonrpsee::core::server::rpc_module::*; use jsonrpsee::types::error::{CallError, ErrorCode, ErrorObject, PARSE_ERROR_CODE}; -use jsonrpsee::types::{EmptyServerParams, ErrorObjectOwned, Params}; +use jsonrpsee::types::{EmptyServerParams, ErrorObjectOwned, Params, SubscriptionEmptyError}; use serde::{Deserialize, Serialize}; use tokio::time::interval; use tokio_stream::wrappers::IntervalStream; @@ -72,7 +72,7 @@ fn flatten_rpc_modules() { #[test] fn rpc_context_modules_can_register_subscriptions() { let mut cxmodule = RpcModule::new(()); - cxmodule.register_subscription("hi", "hi", "goodbye", |_, _, _| Ok(())).unwrap(); + cxmodule.register_subscription("hi", "hi", "goodbye", |_, _, _| async { Ok(()) }).unwrap(); assert!(cxmodule.method("hi").is_some()); assert!(cxmodule.method("goodbye").is_some()); @@ -233,21 +233,20 @@ async fn subscribing_without_server() { let mut module = RpcModule::new(()); module - .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| { + .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| async move { let mut stream_data = vec!['0', '1', '2']; - tokio::spawn(async move { - let sink = pending.accept().await.unwrap(); + let sink = pending.accept().await.unwrap(); + + while let Some(letter) = stream_data.pop() { + tracing::debug!("This is your friendly subscription sending data."); + let msg = sink.build_message(&letter).unwrap(); + let _ = sink.send(msg).await.unwrap(); + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + } + let close = ErrorObject::borrowed(0, &"closed successfully", None); + let _ = sink.close(close.into_owned()).await; - while let Some(letter) = stream_data.pop() { - tracing::debug!("This is your friendly subscription sending data."); - let msg = sink.build_message(&letter).unwrap(); - let _ = sink.send(msg).await.unwrap(); - tokio::time::sleep(std::time::Duration::from_millis(500)).await; - } - let close = ErrorObject::borrowed(0, &"closed successfully", None); - let _ = sink.close(close.into_owned()).await; - }); Ok(()) }) .unwrap(); @@ -268,23 +267,21 @@ async fn close_test_subscribing_without_server() { let mut module = RpcModule::new(()); module - .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| { - tokio::spawn(async move { - let sink = pending.accept().await.unwrap(); - let msg = sink.build_message(&"lo").unwrap(); - - // make sure to only send one item - sink.send(msg.clone()).await.unwrap(); - while !sink.is_closed() { - tracing::debug!("[test] Sink is open, sleeping"); - tokio::time::sleep(std::time::Duration::from_millis(500)).await; - } + .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| async move { + let sink = pending.accept().await.unwrap(); + let msg = sink.build_message(&"lo").unwrap(); + + // make sure to only send one item + sink.send(msg.clone()).await.unwrap(); + while !sink.is_closed() { + tracing::debug!("[test] Sink is open, sleeping"); + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + } - match sink.send(msg).await { - Ok(_) => panic!("The sink should be closed"), - Err(DisconnectError(_)) => {} - } - }); + match sink.send(msg).await { + Ok(_) => panic!("The sink should be closed"), + Err(DisconnectError(_)) => {} + } Ok(()) }) .unwrap(); @@ -317,24 +314,19 @@ async fn close_test_subscribing_without_server() { async fn subscribing_without_server_bad_params() { let mut module = RpcModule::new(()); module - .register_subscription("my_sub", "my_sub", "my_unsub", |params, pending, _| { - let params = params.into_owned(); - let fut = async move { - let p = match params.one::() { - Ok(p) => p, - Err(e) => { - let err: ErrorObjectOwned = e.into(); - let _ = pending.reject(err).await; - return; - } - }; - - let sink = pending.accept().await.unwrap(); - let msg = sink.build_message(&p).unwrap(); - sink.send(msg).await.unwrap(); + .register_subscription("my_sub", "my_sub", "my_unsub", |params, pending, _| async move { + let p = match params.one::() { + Ok(p) => p, + Err(e) => { + let err: ErrorObjectOwned = e.into(); + let _ = pending.reject(err).await; + return Err(SubscriptionEmptyError.into()); + } }; - tokio::spawn(fut); + let sink = pending.accept().await.unwrap(); + let msg = sink.build_message(&p).unwrap(); + sink.send(msg).await.unwrap(); Ok(()) }) @@ -351,20 +343,18 @@ async fn subscribing_without_server_bad_params() { async fn subscribe_unsubscribe_without_server() { let mut module = RpcModule::new(()); module - .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| { + .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| async move { let interval = interval(Duration::from_millis(200)); let mut stream = IntervalStream::new(interval).map(move |_| 1); - tokio::spawn(async move { - let sink = pending.accept().await.unwrap(); + let sink = pending.accept().await.unwrap(); - while let Some(item) = stream.next().await { - let msg = sink.build_message(&item).unwrap(); - if sink.send(msg).await.is_ok() { - return; - } + while let Some(item) = stream.next().await { + let msg = sink.build_message(&item).unwrap(); + if sink.send(msg).await.is_err() { + return Ok(()); } - }); + } Ok(()) }) .unwrap(); @@ -399,11 +389,9 @@ async fn subscribe_unsubscribe_without_server() { async fn rejected_subscription_without_server() { let mut module = RpcModule::new(()); module - .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| { - tokio::spawn(async move { - let err = ErrorObject::borrowed(PARSE_ERROR_CODE, &"rejected", None); - let _ = pending.reject(err.into_owned()).await; - }); + .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| async move { + let err = ErrorObject::borrowed(PARSE_ERROR_CODE, &"rejected", None); + let _ = pending.reject(err.into_owned()).await; Ok(()) }) @@ -419,12 +407,10 @@ async fn rejected_subscription_without_server() { async fn reject_works() { let mut module = RpcModule::new(()); module - .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| { - tokio::spawn(async move { - let err = ErrorObject::borrowed(PARSE_ERROR_CODE, &"rejected", None); - let res = pending.reject(err.into_owned()).await; - assert!(matches!(res, Ok(()))); - }); + .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| async move { + let err = ErrorObject::borrowed(PARSE_ERROR_CODE, &"rejected", None); + let res = pending.reject(err.into_owned()).await; + assert!(matches!(res, Ok(()))); Ok(()) }) From a77a66ad80a5696bc263638ef9ce4bbcd6bfb0fd Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 26 Jan 2023 10:29:08 +0100 Subject: [PATCH 16/60] fix tests --- benches/helpers.rs | 6 +- core/src/server/rpc_module.rs | 131 +++++++++--------- examples/examples/proc_macro.rs | 8 ++ proc-macros/src/helpers.rs | 4 +- proc-macros/src/lib.rs | 39 +++--- proc-macros/src/rpc_macro.rs | 2 +- .../ui/correct/alias_doesnt_use_namespace.rs | 2 +- proc-macros/tests/ui/correct/basic.rs | 38 +++-- proc-macros/tests/ui/correct/only_client.rs | 4 +- proc-macros/tests/ui/correct/only_server.rs | 16 ++- .../tests/ui/correct/parse_angle_brackets.rs | 2 +- .../tests/ui/correct/rpc_deny_missing_docs.rs | 2 +- .../tests/ui/incorrect/sub/sub_async.rs | 10 -- .../tests/ui/incorrect/sub/sub_async.stderr | 6 - .../ui/incorrect/sub/sub_conflicting_alias.rs | 2 +- .../sub/sub_conflicting_alias.stderr | 6 +- .../ui/incorrect/sub/sub_dup_name_override.rs | 4 +- .../sub/sub_dup_name_override.stderr | 6 +- .../tests/ui/incorrect/sub/sub_empty_attr.rs | 2 +- .../ui/incorrect/sub/sub_name_override.rs | 2 +- .../ui/incorrect/sub/sub_name_override.stderr | 6 +- .../tests/ui/incorrect/sub/sub_no_item.rs | 2 +- .../tests/ui/incorrect/sub/sub_no_name.rs | 2 +- .../ui/incorrect/sub/sub_unsupported_field.rs | 2 +- server/src/server.rs | 32 +++-- server/src/transport/ws.rs | 28 ++-- 26 files changed, 201 insertions(+), 163 deletions(-) delete mode 100644 proc-macros/tests/ui/incorrect/sub/sub_async.rs delete mode 100644 proc-macros/tests/ui/incorrect/sub/sub_async.stderr diff --git a/benches/helpers.rs b/benches/helpers.rs index d172c801e0..223fa968e3 100644 --- a/benches/helpers.rs +++ b/benches/helpers.rs @@ -132,7 +132,7 @@ pub async fn http_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee:: /// Run jsonrpsee WebSocket server for benchmarks. #[cfg(not(feature = "jsonrpc-crate"))] pub async fn ws_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee::server::ServerHandle) { - use jsonrpsee::server::ServerBuilder; + use jsonrpsee::{core::server::rpc_module::SubscriptionMessage, server::ServerBuilder}; let server = ServerBuilder::default() .max_request_body_size(u32::MAX) @@ -153,8 +153,8 @@ pub async fn ws_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee::se |_params, pending, _ctx| async move { let x = "Hello"; - let sink = pending.accept().await.unwrap(); - let msg = sink.build_message(&x).unwrap(); + let sink = pending.accept().await?; + let msg = SubscriptionMessage::new(x, sink.method_name(), sink.subscription_id()).unwrap(); sink.send(msg).await.unwrap(); Ok(()) }, diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 135f96aa83..ac64d7fb6e 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -59,7 +59,7 @@ pub type AsyncMethod<'a> = Arc, Params<'a>, ConnectionId, MaxResponseSize) -> BoxFuture<'a, MethodResponse>>; /// Method callback for subscriptions. pub type SubscriptionMethod<'a> = - Arc BoxFuture<'a, MethodResponse>>; + Arc BoxFuture<'a, SubscriptionAnswered>>; // Method callback to unsubscribe. type UnsubscriptionMethod = Arc MethodResponse>; @@ -85,6 +85,18 @@ pub enum TrySendError { Full(SubscriptionMessage), } +#[derive(Debug)] +/// Represents whether a subscription was answered or not. +pub enum SubscriptionAnswered { + /// The subscription was already answered and doesn't need to answered again. + /// The response is kept to be logged. + Yes(MethodResponse), + /// The subscription was never answered and needs to be answered. + /// + /// This may occur if a subscription dropped without calling `PendingSubscriptionSink::accept` or `PendingSubscriptionSink::reject`. + No(MethodResponse), +} + /// Disconnect error #[derive(Debug)] pub struct DisconnectError(pub SubscriptionMessage); @@ -133,6 +145,20 @@ type Subscribers = Arc(result: T, method: &str, sub_id: RpcSubscriptionId) -> Result { + serde_json::to_string(&SubscriptionResponse::new( + method.into(), + SubscriptionPayload { subscription: sub_id, result }, + )) + .map(|json| SubscriptionMessage(json)) + .map_err(Into::into) + } +} + /// Represent a unique subscription entry based on [`RpcSubscriptionId`] and [`ConnectionId`]. #[derive(Clone, Debug, PartialEq, Eq, Hash)] struct SubscriptionKey { @@ -333,13 +359,18 @@ impl Methods { /// use futures_util::StreamExt; /// /// let mut module = RpcModule::new(()); - /// module.register_subscription("hi", "hi", "goodbye", |_, mut sink, _| { - /// sink.send(&"one answer").unwrap(); + /// + /// module.register_subscription("hi", "hi", "goodbye", |_, pending, _| async move { + /// let sink = pending.accept().await?; + /// let msg = sink.build_message(&"one answer").unwrap(); + /// sink.send(msg).await.unwrap(); + /// /// Ok(()) /// }).unwrap(); + /// /// let (resp, mut stream) = module.raw_json_request(r#"{"jsonrpc":"2.0","method":"hi","id":0}"#).await.unwrap(); /// let resp = serde_json::from_str::>(&resp.result).unwrap(); - /// let sub_resp = stream.next().await.unwrap(); + /// let sub_resp = stream.recv().await.unwrap(); /// assert_eq!( /// format!(r#"{{"jsonrpc":"2.0","method":"hi","params":{{"subscription":{},"result":"one answer"}}}}"#, resp.result), /// sub_resp @@ -374,7 +405,10 @@ impl Methods { // The same information is part of `res` above. let _ = rx_sink.recv().await.expect("Every call must at least produce one response; qed"); - res + match res { + SubscriptionAnswered::Yes(r) => r, + SubscriptionAnswered::No(r) => r, + } } Some(MethodKind::Unsubscription(cb)) => (cb)(id, params, 0, usize::MAX), }; @@ -398,9 +432,13 @@ impl Methods { /// use jsonrpsee::{RpcModule, types::EmptyServerParams}; /// /// let mut module = RpcModule::new(()); - /// module.register_subscription("hi", "hi", "goodbye", |_, mut sink, _| { - /// sink.send(&"one answer").unwrap(); + /// module.register_subscription("hi", "hi", "goodbye", |_, pending, _| async move { + /// let sink = pending.accept().await?; + /// + /// let msg = sink.build_message(&"one answer").unwrap(); + /// sink.send(msg).await.unwrap(); /// Ok(()) + /// /// }).unwrap(); /// /// let mut sub = module.subscribe("hi", EmptyServerParams::new()).await.unwrap(); @@ -599,20 +637,15 @@ impl RpcModule { /// use jsonrpsee_core::Error; /// /// let mut ctx = RpcModule::new(99_usize); - /// ctx.register_subscription("sub", "notif_name", "unsub", |params, mut sink, ctx| { - /// let x = match params.one::() { - /// Ok(x) => x, - /// Err(e) => { - /// let err: Error = e.into(); - /// sink.reject(err); - /// return Ok(()); - /// } - /// }; - /// // Sink is accepted on the first `send` call. - /// std::thread::spawn(move || { - /// let sum = x + (*ctx); - /// let _ = sink.send(&sum); - /// }); + /// ctx.register_subscription("sub", "notif_name", "unsub", |params, pending, ctx| async move { + /// let x = params.one::()?; + /// + /// // mark the subscription is accepted after the params has been parsed successful. + /// let sink = pending.accept().await?; + /// + /// let sum = x + (*ctx); + /// let msg = sink.build_message(&sum).unwrap(); + /// sink.send(msg).await.unwrap(); /// /// Ok(()) /// }); @@ -706,8 +739,11 @@ impl RpcModule { let result = async move { match rx.await { - Ok(result) => result, - Err(_) => MethodResponse::error(id, ErrorObject::from(ErrorCode::InternalError)), + Ok(r) => SubscriptionAnswered::Yes(r), + Err(_) => { + let response = MethodResponse::error(id, ErrorObject::from(ErrorCode::InternalError)); + SubscriptionAnswered::No(response) + } } }; @@ -762,7 +798,10 @@ impl IsUnsubscribed { /// Represents a single subscription that is waiting to be accepted or rejected. /// -/// You must either call `accept` or `reject` otherwise this does nothing. +/// If this is dropped without calling `PendingSubscription::reject` or `PendingSubscriptionSink::accept` +/// a default error is sent out as response to the subscription call. +/// +/// Thus, if you want a customized error message then `PendingSubscription::reject` must be called. #[derive(Debug)] #[must_use = "PendningSubscriptionSink does nothing unless `accept` or `reject` is called"] pub struct PendingSubscriptionSink { @@ -844,8 +883,6 @@ pub struct SubscriptionSink { impl SubscriptionSink { /// Get the subscription ID. - /// - /// [`SubscriptionSink::accept`] should be called prior to this method. pub fn subscription_id(&self) -> RpcSubscriptionId<'static> { self.uniq_sub.sub_id.clone() } @@ -870,20 +907,6 @@ impl SubscriptionSink { self.inner.send(msg.0).await.map_err(Into::into) } - /// Similar to `SubscriptionSink::try_send` but it encodes the message as - /// a JSON-RPC subscription notification. - /// The error could either be encode error or send error. - /// - /// NOTE: if this fails you will get back the message as JSON string and if you want - /// to re-send then `SubscriptionSink::send` must be used. - // - // TODO(niklasad1): do we want this API?!. I don't but a bit more code to use the new APIs - // for users as the message needs to be serialized by the user of API. - pub async fn encode_and_send(&self, val: &T) -> Result<(), ()> { - let msg = self.build_message(val).map_err(|_| ())?; - self.send(msg).await.map_err(|_| ()) - } - /// Attempts to immediately send out the message as JSON string to the subscribers but fails if the /// channel is full, that the connection is closed or the subscription was not explicitly accepted. /// @@ -899,20 +922,6 @@ impl SubscriptionSink { self.inner.try_send(msg.0).map_err(Into::into) } - /// Similar to `SubscriptionSink::try_send` but it encodes the message as - /// a JSON-RPC subscription notification. - /// The error could either be encode error or send error. - // - /// NOTE: if this fails you will get back the message as JSON string and if you want - /// to re-send then `SubscriptionSink::send` must be used. - // - // TODO(niklasad1): do we want this API?!. I don't but a bit more code to use the new APIs - // for users as the message needs to be serialized by the user of API. - pub fn encode_and_try_send(&mut self, val: &T) -> Result<(), ()> { - let msg = self.build_message(val).map_err(|_| ())?; - self.try_send(msg).map_err(|_| ()) - } - /// Returns whether the subscription is closed. pub fn is_closed(&self) -> bool { self.inner.is_closed() || !self.is_active_subscription() @@ -927,18 +936,14 @@ impl SubscriptionSink { } } - /// ... + /// Build message that will be serialized as JSON-RPC notification. + /// + /// You need to call this method prior to send out a subscription notification. pub fn build_message(&self, result: &T) -> Result { - serde_json::to_string(&SubscriptionResponse::new( - self.method.into(), - SubscriptionPayload { subscription: self.uniq_sub.sub_id.clone(), result }, - )) - .map(|json| SubscriptionMessage(json)) - .map_err(Into::into) + SubscriptionMessage::new(result, self.method, self.uniq_sub.sub_id.clone()) } - /// ... - pub fn build_error_message(&self, error: &T) -> Result { + fn build_error_message(&self, error: &T) -> Result { serde_json::to_string(&SubscriptionError::new( self.method.into(), SubscriptionPayloadError { subscription: self.uniq_sub.sub_id.clone(), error }, diff --git a/examples/examples/proc_macro.rs b/examples/examples/proc_macro.rs index 43b7ec73a7..a856c871e4 100644 --- a/examples/examples/proc_macro.rs +++ b/examples/examples/proc_macro.rs @@ -35,6 +35,14 @@ use jsonrpsee::ws_client::WsClientBuilder; type ExampleHash = [u8; 32]; type ExampleStorageKey = Vec; +#[rpc(client, server)] +pub trait DupOverride { + #[subscription(name = "subscribeOne" => "override", item = u8)] + async fn one(&self) -> jsonrpsee::core::SubscriptionResult; + /*#[subscription(name = "subscribeTwo" => "override", item = u8)] + async fn two(&self) -> jsonrpsee::core::SubscriptionResult;*/ +} + #[rpc(server, client, namespace = "state")] pub trait Rpc where diff --git a/proc-macros/src/helpers.rs b/proc-macros/src/helpers.rs index 5e5492c1bb..c1e5fb2e8c 100644 --- a/proc-macros/src/helpers.rs +++ b/proc-macros/src/helpers.rs @@ -71,7 +71,7 @@ fn find_jsonrpsee_crate(http_name: &str, ws_name: &str) -> Result { @@ -79,7 +79,7 @@ fn find_jsonrpsee_crate(http_name: &str, ws_name: &str) -> Result RpcResult; /// /// #[subscription(name = "subscribe", item = Vec)] -/// fn sub(&self); +/// async fn sub(&self) -> SubscriptionResult; /// } /// ``` /// diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index fa3b3fab7b..d55746daaf 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -86,7 +86,7 @@ pub(crate) mod visitor; /// fn sync_method(&self) -> String; /// /// #[subscription(name = "subscribe", item = "String")] -/// fn sub(&self); +/// async fn sub(&self) -> SubscriptionResult; /// } /// ``` /// @@ -99,8 +99,8 @@ pub(crate) mod visitor; /// async fn async_method(&self, param_a: u8, param_b: String) -> u16; /// fn sync_method(&self) -> String; /// -/// // Note that `subscription_sink` and `SubscriptionResult` were added automatically. -/// fn sub(&self, subscription_sink: SubscriptionResult) -> SubscriptionResult; +/// // Note that `pending subscription sink` was added automatically. +/// async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult; /// /// fn into_rpc(self) -> Result { /// // Actual implementation stripped, but inside we will create @@ -219,7 +219,7 @@ pub(crate) mod visitor; /// /// // RPC is put into a separate module to clearly show names of generated entities. /// mod rpc_impl { -/// use jsonrpsee::{proc_macros::rpc, core::async_trait, core::RpcResult, server::SubscriptionSink}; +/// use jsonrpsee::{proc_macros::rpc, core::async_trait, core::RpcResult, server::PendingSubscriptionSink}; /// use jsonrpsee::types::SubscriptionResult; /// /// // Generate both server and client implementations, prepend all the methods with `foo_` prefix. @@ -248,7 +248,7 @@ pub(crate) mod visitor; /// /// } /// /// ``` /// #[subscription(name = "sub" => "subNotif", unsubscribe = "unsub", item = String)] -/// fn sub_override_notif_method(&self); +/// async fn sub_override_notif_method(&self) -> SubscriptionResult; /// /// /// Use the same method name for both the `subscribe call` and `notifications` /// /// @@ -265,7 +265,7 @@ pub(crate) mod visitor; /// /// } /// /// ``` /// #[subscription(name = "subscribe", item = String)] -/// fn sub(&self); +/// async fn sub(&self) -> SubscriptionResult; /// } /// /// // Structure that will implement the `MyRpcServer` trait. @@ -292,20 +292,25 @@ pub(crate) mod visitor; /// /// // The stream API can be used to pipe items from the underlying stream /// // as subscription responses. -/// fn sub_override_notif_method(&self, mut sink: SubscriptionSink) -> SubscriptionResult { -/// tokio::spawn(async move { -/// let stream = futures_util::stream::iter(["one", "two", "three"]); -/// sink.pipe_from_stream(stream).await; -/// }); +/// async fn sub_override_notif_method(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { +/// let sink = pending.accept().await?; +/// +/// //let stream = futures_util::stream::iter(["one", "two", "three"]); +/// //sink.pipe_from_stream(stream).await; +/// /// Ok(()) /// } /// -/// // We could've spawned a `tokio` future that yields values while our program works, -/// // but for simplicity of the example we will only send two values and then close -/// // the subscription. -/// fn sub(&self, mut sink: SubscriptionSink) -> SubscriptionResult { -/// let _ = sink.send(&"Response_A"); -/// let _ = sink.send(&"Response_B"); +/// // Send out two values on the subscription. +/// async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { +/// let sink = pending.accept().await?; +/// +/// let msg1 = sink.build_message(&"Response_A").unwrap(); +/// let msg2 = sink.build_message(&"Response_B").unwrap(); +/// +/// sink.send(msg1).await.unwrap(); +/// sink.send(msg2).await.unwrap(); +/// /// Ok(()) /// } /// } diff --git a/proc-macros/src/rpc_macro.rs b/proc-macros/src/rpc_macro.rs index 6dfa086c90..b263fc1459 100644 --- a/proc-macros/src/rpc_macro.rs +++ b/proc-macros/src/rpc_macro.rs @@ -311,7 +311,7 @@ impl RpcDescription { _ => { return Err(syn::Error::new_spanned( method, - "Subscription methods must return `SubscriptionResult`", + "Subscription methods must return `SubscriptionResult` or `Result`", )); } }; diff --git a/proc-macros/tests/ui/correct/alias_doesnt_use_namespace.rs b/proc-macros/tests/ui/correct/alias_doesnt_use_namespace.rs index f68be69dcc..fdaba5b742 100644 --- a/proc-macros/tests/ui/correct/alias_doesnt_use_namespace.rs +++ b/proc-macros/tests/ui/correct/alias_doesnt_use_namespace.rs @@ -8,7 +8,7 @@ pub trait Rpc { async fn async_method(&self, param_a: u8, param_b: String) -> RpcResult; #[subscription(name = "subscribeGetFood", item = String, aliases = ["getFood"], unsubscribe_aliases = ["unsubscribegetFood"])] - fn sub(&self); + async fn sub(&self) -> SubscriptionResult; } fn main() {} diff --git a/proc-macros/tests/ui/correct/basic.rs b/proc-macros/tests/ui/correct/basic.rs index e8a58e97c9..4f8ef2a298 100644 --- a/proc-macros/tests/ui/correct/basic.rs +++ b/proc-macros/tests/ui/correct/basic.rs @@ -8,7 +8,7 @@ use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::ServerBuilder; use jsonrpsee::types::SubscriptionResult; use jsonrpsee::ws_client::*; -use jsonrpsee::{rpc_params, SubscriptionSink}; +use jsonrpsee::{rpc_params, PendingSubscriptionSink}; #[rpc(client, server, namespace = "foo")] pub trait Rpc { @@ -28,15 +28,15 @@ pub trait Rpc { fn sync_method(&self) -> RpcResult; #[subscription(name = "subscribe", item = String)] - fn sub(&self); + async fn sub(&self) -> SubscriptionResult; #[subscription(name = "echo", unsubscribe = "unsubscribeEcho", aliases = ["ECHO"], item = u32, unsubscribe_aliases = ["NotInterested", "listenNoMore"])] - fn sub_with_params(&self, val: u32); + async fn sub_with_params(&self, val: u32) -> SubscriptionResult; // This will send data to subscribers with the `method` field in the JSON payload set to `foo_subscribe_override` // because it's in the `foo` namespace. #[subscription(name = "subscribe_method" => "subscribe_override", item = u32)] - fn sub_with_override_notif_method(&self); + async fn sub_with_override_notif_method(&self) -> SubscriptionResult; } pub struct RpcServerImpl; @@ -65,20 +65,34 @@ impl RpcServer for RpcServerImpl { Ok(10u16) } - fn sub(&self, mut sink: SubscriptionSink) -> SubscriptionResult { - let _ = sink.send(&"Response_A"); - let _ = sink.send(&"Response_B"); + async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { + let sink = pending.accept().await?; + + let msg1 = sink.build_message(&"Response_A").unwrap(); + let msg2 = sink.build_message(&"Response_B").unwrap(); + + sink.send(msg1).await.unwrap(); + sink.send(msg2).await.unwrap(); + Ok(()) } - fn sub_with_params(&self, mut sink: SubscriptionSink, val: u32) -> SubscriptionResult { - let _ = sink.send(&val); - let _ = sink.send(&val); + async fn sub_with_params(&self, pending: PendingSubscriptionSink, val: u32) -> SubscriptionResult { + let sink = pending.accept().await?; + + let msg = sink.build_message(&val).unwrap(); + + sink.send(msg.clone()).await.unwrap(); + sink.send(msg).await.unwrap(); + Ok(()) } - fn sub_with_override_notif_method(&self, mut sink: SubscriptionSink) -> SubscriptionResult { - let _ = sink.send(&1); + async fn sub_with_override_notif_method(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { + let sink = pending.accept().await?; + let msg = sink.build_message(&1).unwrap(); + sink.send(msg).await.unwrap(); + Ok(()) } } diff --git a/proc-macros/tests/ui/correct/only_client.rs b/proc-macros/tests/ui/correct/only_client.rs index 05bedba2b5..7d8e9bd852 100644 --- a/proc-macros/tests/ui/correct/only_client.rs +++ b/proc-macros/tests/ui/correct/only_client.rs @@ -1,4 +1,4 @@ -//! Example of using proc macro to generate working client and server. +//! Example of using proc macro to generate working client. use jsonrpsee::{core::RpcResult, proc_macros::rpc}; @@ -11,7 +11,7 @@ pub trait Rpc { fn sync_method(&self) -> RpcResult; #[subscription(name = "subscribe", item = String)] - fn sub(&self); + async fn sub(&self) -> SubscriptionResult; } fn main() {} diff --git a/proc-macros/tests/ui/correct/only_server.rs b/proc-macros/tests/ui/correct/only_server.rs index 45f251688e..b9438f7dd8 100644 --- a/proc-macros/tests/ui/correct/only_server.rs +++ b/proc-macros/tests/ui/correct/only_server.rs @@ -2,7 +2,7 @@ use std::net::SocketAddr; use jsonrpsee::core::{async_trait, RpcResult}; use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::{ServerBuilder, SubscriptionSink}; +use jsonrpsee::server::{PendingSubscriptionSink, ServerBuilder}; use jsonrpsee::types::SubscriptionResult; #[rpc(server)] @@ -14,7 +14,7 @@ pub trait Rpc { fn sync_method(&self) -> RpcResult; #[subscription(name = "subscribe", item = String)] - fn sub(&self); + async fn sub(&self) -> SubscriptionResult; } pub struct RpcServerImpl; @@ -29,11 +29,15 @@ impl RpcServer for RpcServerImpl { Ok(10u16) } - fn sub(&self, mut sink: SubscriptionSink) -> SubscriptionResult { - sink.accept()?; + async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { + let sink = pending.accept().await?; + + let msg1 = sink.build_message(&"Response_A").unwrap(); + let msg2 = sink.build_message(&"Response_B").unwrap(); + + sink.send(msg1).await.unwrap(); + sink.send(msg2).await.unwrap(); - let _ = sink.send(&"Response_A"); - let _ = sink.send(&"Response_B"); Ok(()) } } diff --git a/proc-macros/tests/ui/correct/parse_angle_brackets.rs b/proc-macros/tests/ui/correct/parse_angle_brackets.rs index 24061b2095..92b6178d09 100644 --- a/proc-macros/tests/ui/correct/parse_angle_brackets.rs +++ b/proc-macros/tests/ui/correct/parse_angle_brackets.rs @@ -12,6 +12,6 @@ fn main() { // angle braces need to be accounted for manually. item = TransactionStatus, )] - fn dummy_subscription(&self); + async fn dummy_subscription(&self) -> jsonrpsee::types::SubscriptionResult; } } diff --git a/proc-macros/tests/ui/correct/rpc_deny_missing_docs.rs b/proc-macros/tests/ui/correct/rpc_deny_missing_docs.rs index ee61fa6bfe..11113a5961 100644 --- a/proc-macros/tests/ui/correct/rpc_deny_missing_docs.rs +++ b/proc-macros/tests/ui/correct/rpc_deny_missing_docs.rs @@ -13,7 +13,7 @@ pub trait ApiWithDocumentation { /// Subscription docs. #[subscription(name = "sub", unsubscribe = "unsub", item = String)] - fn sub(&self); + async fn sub(&self) -> jsonrpsee::types::SubscriptionResult; } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_async.rs b/proc-macros/tests/ui/incorrect/sub/sub_async.rs deleted file mode 100644 index b77d9ed945..0000000000 --- a/proc-macros/tests/ui/incorrect/sub/sub_async.rs +++ /dev/null @@ -1,10 +0,0 @@ -use jsonrpsee::proc_macros::rpc; - -// Subscription method must not be async. -#[rpc(client, server)] -pub trait AsyncSub { - #[subscription(name = "sub", item = u8)] - async fn sub(&self); -} - -fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_async.stderr b/proc-macros/tests/ui/incorrect/sub/sub_async.stderr deleted file mode 100644 index 352c2410dd..0000000000 --- a/proc-macros/tests/ui/incorrect/sub/sub_async.stderr +++ /dev/null @@ -1,6 +0,0 @@ -error: Subscription methods must not be `async` - --> $DIR/sub_async.rs:6:2 - | -6 | / #[subscription(name = "sub", item = u8)] -7 | | async fn sub(&self); - | |________________________^ diff --git a/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.rs b/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.rs index 119c1a1a45..ba1f5c38f7 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.rs @@ -3,7 +3,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait DuplicatedSubAlias { #[subscription(name = "subscribeAlias", item = String, aliases = ["hello_is_goodbye"], unsubscribe_aliases = ["hello_is_goodbye"])] - fn async_method(&self); + async fn async_method(&self) -> jsonrpsee::core::SubscriptionResult; } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.stderr b/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.stderr index 7c08822b38..d1b1582af5 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.stderr +++ b/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.stderr @@ -1,5 +1,5 @@ error: "hello_is_goodbye" is already defined - --> $DIR/sub_conflicting_alias.rs:6:5 + --> tests/ui/incorrect/sub/sub_conflicting_alias.rs:6:11 | -6 | fn async_method(&self); - | ^^^^^^^^^^^^ +6 | async fn async_method(&self) -> jsonrpsee::core::SubscriptionResult; + | ^^^^^^^^^^^^ diff --git a/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.rs b/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.rs index cce07c544e..dba56b1a02 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.rs @@ -4,9 +4,9 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait DupOverride { #[subscription(name = "subscribeOne" => "override", item = u8)] - fn one(&self); + async fn one(&self) -> jsonrpsee::core::SubscriptionResult; #[subscription(name = "subscribeTwo" => "override", item = u8)] - fn two(&self); + async fn two(&self) -> jsonrpsee::core::SubscriptionResult; } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.stderr b/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.stderr index b17dba0119..a603a6f982 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.stderr +++ b/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.stderr @@ -1,5 +1,5 @@ error: "override" is already defined - --> $DIR/sub_dup_name_override.rs:9:5 + --> $DIR/sub_dup_name_override.rs:9:11 | -9 | fn two(&self); - | ^^^ +9 | async fn two(&self) -> jsonrpsee::core::SubscriptionResult; + | ^^^ diff --git a/proc-macros/tests/ui/incorrect/sub/sub_empty_attr.rs b/proc-macros/tests/ui/incorrect/sub/sub_empty_attr.rs index 55124fe914..9177c94c8a 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_empty_attr.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_empty_attr.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait SubEmptyAttr { #[subscription()] - fn sub(&self); + async fn sub(&self) -> jsonrpsee::types::SubscriptionResult; } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_name_override.rs b/proc-macros/tests/ui/incorrect/sub/sub_name_override.rs index f46c313c9f..a71eda9a42 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_name_override.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_name_override.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait DupName { #[subscription(name = "one" => "one", unsubscribe = "unsubscribeOne", item = u8)] - fn one(&self); + async fn one(&self) -> jsonrpsee::types::SubscriptionResult; } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_name_override.stderr b/proc-macros/tests/ui/incorrect/sub/sub_name_override.stderr index cfdd2afbbe..496eecb1fc 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_name_override.stderr +++ b/proc-macros/tests/ui/incorrect/sub/sub_name_override.stderr @@ -1,5 +1,5 @@ error: "one" is already defined - --> $DIR/sub_name_override.rs:7:5 + --> $DIR/sub_name_override.rs:7:11 | -7 | fn one(&self); - | ^^^ +7 | async fn one(&self) -> jsonrpsee::types::SubscriptionResult; + | ^^^ diff --git a/proc-macros/tests/ui/incorrect/sub/sub_no_item.rs b/proc-macros/tests/ui/incorrect/sub/sub_no_item.rs index fbb65eff3f..b31f947fb5 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_no_item.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_no_item.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait NoSubItem { #[subscription(name = "sub")] - fn sub(&self); + async fn sub(&self) -> jsonrpsee::types::SubscriptionResult; } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_no_name.rs b/proc-macros/tests/ui/incorrect/sub/sub_no_name.rs index 3bab992b0b..900a4488c8 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_no_name.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_no_name.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait NoSubName { #[subscription(item = String)] - fn async_method(&self); + async fn async_method(&self) -> SubscriptionResult; } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.rs b/proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.rs index be5c9472be..5835512eac 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait UnsupportedField { #[subscription(name = "sub", unsubscribe = "unsub", item = u8, magic = true)] - fn sub(&self); + async fn sub(&self) -> SubscriptionResult; } fn main() {} diff --git a/server/src/server.rs b/server/src/server.rs index 68f64de677..311cbe6060 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -44,7 +44,7 @@ use jsonrpsee_core::id_providers::RandomIntegerIdProvider; use jsonrpsee_core::server::helpers::MethodResponse; use jsonrpsee_core::server::host_filtering::AllowHosts; -use jsonrpsee_core::server::rpc_module::Methods; +use jsonrpsee_core::server::rpc_module::{Methods, SubscriptionAnswered}; use jsonrpsee_core::traits::IdProvider; use jsonrpsee_core::{http_helpers, Error, TEN_MB_SIZE_BYTES}; @@ -502,23 +502,37 @@ impl Builder { } } +/// This represent a response to a RPC call +/// and `Subscribe` calls are handled differently +/// because we want to prevent subscriptions to start +/// before the actual subscription call has been answered. pub(crate) enum MethodResult { - JustLogger(MethodResponse), - SendAndLogger(MethodResponse), + /// The subscription callback itself sends back the result + /// so it must not be sent back again. + Subscribe(SubscriptionAnswered), + + /// Treat it as ordinary call. + Call(MethodResponse), } impl MethodResult { - pub(crate) fn as_inner(&self) -> &MethodResponse { + pub(crate) fn as_response(&self) -> &MethodResponse { match &self { - Self::JustLogger(r) => r, - Self::SendAndLogger(r) => r, + Self::Subscribe(r) => match r { + SubscriptionAnswered::Yes(r) => r, + SubscriptionAnswered::No(r) => r, + }, + Self::Call(r) => r, } } - pub(crate) fn into_inner(self) -> MethodResponse { + pub(crate) fn into_response(self) -> MethodResponse { match self { - Self::JustLogger(r) => r, - Self::SendAndLogger(r) => r, + Self::Subscribe(r) => match r { + SubscriptionAnswered::Yes(r) => r, + SubscriptionAnswered::No(r) => r, + }, + Self::Call(r) => r, } } } diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 3ea80beaf2..4d03ad3946 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -12,7 +12,7 @@ use futures_util::stream::FuturesOrdered; use futures_util::{Future, FutureExt, StreamExt}; use hyper::upgrade::Upgraded; use jsonrpsee_core::server::helpers::{prepare_error, BatchResponse, BatchResponseBuilder, MethodResponse, MethodSink}; -use jsonrpsee_core::server::rpc_module::{ConnState, MethodKind, Methods}; +use jsonrpsee_core::server::rpc_module::{ConnState, MethodKind, Methods, SubscriptionAnswered}; use jsonrpsee_core::tracing::{rx_log_from_json, tx_log_from_str}; use jsonrpsee_core::traits::IdProvider; use jsonrpsee_core::{Error, JsonRawValue}; @@ -116,7 +116,7 @@ pub(crate) async fn process_batch_request(b: Batch<'_, L>) -> Option< .into_iter() .filter_map(|v| { if let Ok(req) = serde_json::from_str::(v.get()) { - Some(Either::Right(async { execute_call(req, call.clone()).await.into_inner() })) + Some(Either::Right(async { execute_call(req, call.clone()).await.into_response() })) } else if let Ok(_notif) = serde_json::from_str::>(v.get()) { // notifications should not be answered. got_notif = true; @@ -156,7 +156,7 @@ pub(crate) async fn process_single_request(data: Vec, call: CallD execute_call_with_tracing(req, call).await } else { let (id, code) = prepare_error(&data); - MethodResult::SendAndLogger(MethodResponse::error(id, ErrorObject::from(code))) + MethodResult::Call(MethodResponse::error(id, ErrorObject::from(code))) } } @@ -184,12 +184,12 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData None => { logger.on_call(name, params.clone(), logger::MethodKind::Unknown, TransportProtocol::WebSocket); let response = MethodResponse::error(id, ErrorObject::from(ErrorCode::MethodNotFound)); - MethodResult::SendAndLogger(response) + MethodResult::Call(response) } Some((name, method)) => match &method.inner() { MethodKind::Sync(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::MethodCall, TransportProtocol::WebSocket); - MethodResult::SendAndLogger((callback)(id, params, max_response_body_size as usize)) + MethodResult::Call((callback)(id, params, max_response_body_size as usize)) } MethodKind::Async(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::MethodCall, TransportProtocol::WebSocket); @@ -198,27 +198,27 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData let params = params.into_owned(); let response = (callback)(id, params, conn_id, max_response_body_size as usize).await; - MethodResult::SendAndLogger(response) + MethodResult::Call(response) } MethodKind::Subscription(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::Subscription, TransportProtocol::WebSocket); let conn_state = ConnState { conn_id, id_provider }; let response = callback(id.clone(), params, sink.clone(), conn_state).await; - // TODO(niklasad1): investigate why we need that. - MethodResult::JustLogger(response) + + MethodResult::Subscribe(response) } MethodKind::Unsubscription(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::Unsubscription, TransportProtocol::WebSocket); // Don't adhere to any resource or subscription limits; always let unsubscribing happen! let result = callback(id, params, conn_id, max_response_body_size as usize); - MethodResult::SendAndLogger(result) + MethodResult::Call(result) } }, }; - let r = response.as_inner(); + let r = response.as_response(); tx_log_from_str(&r.result, max_log_length); logger.on_result(name, r.success, request_start, TransportProtocol::WebSocket); @@ -344,10 +344,14 @@ pub(crate) async fn background_task( }; match process_single_request(data, call).await { - MethodResult::JustLogger(r) => { + MethodResult::Subscribe(SubscriptionAnswered::Yes(r)) => { + logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); + } + MethodResult::Subscribe(SubscriptionAnswered::No(r)) => { logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); + sink_permit.send_raw(r.result); } - MethodResult::SendAndLogger(r) => { + MethodResult::Call(r) => { logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); sink_permit.send_raw(r.result); } From 7e306b3ceff6b4bfe87fe6308b3d18ae9b94ff09 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 26 Jan 2023 12:31:07 +0100 Subject: [PATCH 17/60] move non-jsonrpc spec types from types --- core/src/error.rs | 56 +++++++++++++++++++ core/src/lib.rs | 10 +++- core/src/server/rpc_module.rs | 15 ++--- examples/examples/proc_macro.rs | 3 +- examples/examples/ws_pubsub_broadcast.rs | 6 +- examples/examples/ws_pubsub_with_params.rs | 10 ++-- proc-macros/src/helpers.rs | 3 +- proc-macros/src/lib.rs | 4 +- proc-macros/src/render_server.rs | 4 +- proc-macros/tests/ui/correct/basic.rs | 2 +- proc-macros/tests/ui/correct/only_server.rs | 3 +- .../tests/ui/correct/parse_angle_brackets.rs | 2 +- .../tests/ui/correct/rpc_deny_missing_docs.rs | 2 +- .../tests/ui/incorrect/sub/sub_empty_attr.rs | 2 +- .../ui/incorrect/sub/sub_name_override.rs | 2 +- .../ui/incorrect/sub/sub_name_override.stderr | 2 +- .../tests/ui/incorrect/sub/sub_no_item.rs | 2 +- tests/tests/helpers.rs | 36 ++++-------- tests/tests/proc_macros.rs | 5 +- tests/tests/rpc_module.rs | 5 +- types/src/error.rs | 46 --------------- types/src/lib.rs | 5 +- 22 files changed, 111 insertions(+), 114 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index a6864dd45f..60acc453c7 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -217,3 +217,59 @@ impl From for Error { Error::Transport(hyper_err.into()) } } + +/// The error returned by the subscription's method for the rpc server implementation. +/// +/// It contains no data, and neither is the error utilized. It provides an abstraction to make the +/// API more ergonomic while handling errors that may occur during the subscription callback. +#[derive(Debug, Clone, Copy)] +pub struct SubscriptionEmptyError; + +impl From for SubscriptionEmptyError { + fn from(_: anyhow::Error) -> Self { + SubscriptionEmptyError + } +} + +impl From for SubscriptionEmptyError { + fn from(_: CallError) -> Self { + SubscriptionEmptyError + } +} + +impl From for SubscriptionEmptyError { + fn from(_: SubscriptionAcceptRejectError) -> Self { + SubscriptionEmptyError + } +} + +impl From for SubscriptionEmptyError { + fn from(_: serde_json::Error) -> Self { + SubscriptionEmptyError + } +} + +#[cfg(feature = "server")] +impl From for SubscriptionEmptyError { + fn from(_: crate::server::rpc_module::TrySendError) -> Self { + SubscriptionEmptyError + } +} + +#[cfg(feature = "server")] +impl From for SubscriptionEmptyError { + fn from(_: crate::server::rpc_module::DisconnectError) -> Self { + SubscriptionEmptyError + } +} + +/// The error returned while accepting or rejecting a subscription. +#[derive(Debug, Copy, Clone)] +pub enum SubscriptionAcceptRejectError { + /// The method was already called. + AlreadyCalled, + /// The remote peer closed the connection or called the unsubscribe method. + RemotePeerAborted, + /// The subscription response message was too large. + MessageTooLarge, +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 7a626aad04..63144523b4 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -58,11 +58,19 @@ cfg_client! { /// Shared tracing helpers to trace RPC calls. pub mod tracing; pub use async_trait::async_trait; -pub use error::Error; +pub use error::{Error, SubscriptionAcceptRejectError, SubscriptionClosed, SubscriptionEmptyError}; /// JSON-RPC result. pub type RpcResult = std::result::Result; +/// The return type of the subscription's method for the rpc server implementation. +/// +/// **Note**: The error does not contain any data and is discarded on drop. +pub type SubscriptionResult = Result<(), SubscriptionEmptyError>; + +/// Empty server `RpcParams` type to use while registering modules. +pub type EmptyServerParams = Vec<()>; + /// Re-exports for proc-macro library to not require any additional /// dependencies to be explicitly added on the client side. #[doc(hidden)] diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index ac64d7fb6e..070490d700 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -30,17 +30,18 @@ use std::future::Future; use std::ops::{Deref, DerefMut}; use std::sync::Arc; -use crate::error::Error; +use crate::error::{Error, SubscriptionAcceptRejectError}; use crate::id_providers::RandomIntegerIdProvider; use crate::server::helpers::MethodSink; use crate::traits::{IdProvider, ToRpcParams}; +use crate::SubscriptionResult; use futures_util::future::Either; use futures_util::{future::BoxFuture, FutureExt}; -use jsonrpsee_types::error::{CallError, ErrorCode, ErrorObject, ErrorObjectOwned, SubscriptionAcceptRejectError}; +use jsonrpsee_types::error::{CallError, ErrorCode, ErrorObject, ErrorObjectOwned}; use jsonrpsee_types::response::{SubscriptionError, SubscriptionPayloadError}; use jsonrpsee_types::{ ErrorResponse, Id, Params, Request, Response, SubscriptionId as RpcSubscriptionId, SubscriptionPayload, - SubscriptionResponse, SubscriptionResult, + SubscriptionResponse, }; use parking_lot::Mutex; use rustc_hash::FxHashMap; @@ -429,7 +430,7 @@ impl Methods { /// ``` /// #[tokio::main] /// async fn main() { - /// use jsonrpsee::{RpcModule, types::EmptyServerParams}; + /// use jsonrpsee::{RpcModule, core::EmptyServerParams}; /// /// let mut module = RpcModule::new(()); /// module.register_subscription("hi", "hi", "goodbye", |_, pending, _| async move { @@ -980,11 +981,7 @@ impl SubscriptionSink { let msg = self.build_error_message(&err.into()).expect("valid json infallible; qed"); return Either::Right(async move { - let permit = match sink.reserve().await { - Ok(permit) => permit, - Err(_) => return, - }; - permit.send_raw(msg.0); + let _ = sink.send(msg.0).await; }); } } diff --git a/examples/examples/proc_macro.rs b/examples/examples/proc_macro.rs index a856c871e4..5fb13bd18f 100644 --- a/examples/examples/proc_macro.rs +++ b/examples/examples/proc_macro.rs @@ -26,10 +26,9 @@ use std::net::SocketAddr; -use jsonrpsee::core::{async_trait, client::Subscription, Error}; +use jsonrpsee::core::{async_trait, client::Subscription, Error, SubscriptionResult}; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{PendingSubscriptionSink, ServerBuilder}; -use jsonrpsee::types::SubscriptionResult; use jsonrpsee::ws_client::WsClientBuilder; type ExampleHash = [u8; 32]; diff --git a/examples/examples/ws_pubsub_broadcast.rs b/examples/examples/ws_pubsub_broadcast.rs index 9757ef19a0..96ad824403 100644 --- a/examples/examples/ws_pubsub_broadcast.rs +++ b/examples/examples/ws_pubsub_broadcast.rs @@ -28,11 +28,11 @@ use std::net::SocketAddr; -use anyhow::anyhow; use futures::future; use futures::StreamExt; use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; use jsonrpsee::core::server::rpc_module::TrySendError; +use jsonrpsee::core::SubscriptionResult; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, ServerBuilder}; use jsonrpsee::ws_client::WsClientBuilder; @@ -98,8 +98,8 @@ async fn run_server() -> anyhow::Result { async fn pipe_from_stream_with_bounded_buffer( pending: PendingSubscriptionSink, mut stream: BroadcastStream, -) -> anyhow::Result<()> { - let mut sink = pending.accept().await.map_err(|e| anyhow!("{:?}", e))?; +) -> SubscriptionResult { + let mut sink = pending.accept().await?; let mut bounded_buffer = bounded_vec_deque::BoundedVecDeque::new(10); // This is not recommended approach it would be better to have a queue that returns a future once diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index 7d9d259bdd..ba1ee6650b 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -45,7 +45,7 @@ async fn main() -> anyhow::Result<()> { .expect("setting default subscriber failed"); let addr = run_server().await?; - /*let url = format!("ws://{}", addr); + let url = format!("ws://{}", addr); let client = WsClientBuilder::default().build(&url).await?; @@ -57,9 +57,7 @@ async fn main() -> anyhow::Result<()> { // Subscription with multiple parameters let mut sub_params_two: Subscription = client.subscribe("sub_params_two", rpc_params![2, 5], "unsub_params_two").await?; - tracing::info!("subscription with two params: {:?}", sub_params_two.next().await);*/ - - futures::future::pending::<()>().await; + tracing::info!("subscription with two params: {:?}", sub_params_two.next().await); Ok(()) } @@ -105,10 +103,10 @@ async fn run_server() -> anyhow::Result { let interval = interval(Duration::from_millis(200)); let mut stream = IntervalStream::new(interval).map(move |_| item); - let mut sink = pending.accept().await.unwrap(); + let mut sink = pending.accept().await?; while let Some(item) = stream.next().await { - let notif = sink.build_message(&item).unwrap(); + let notif = sink.build_message(&item)?; match sink.try_send(notif) { Ok(_) => (), Err(TrySendError::Closed(m)) => { diff --git a/proc-macros/src/helpers.rs b/proc-macros/src/helpers.rs index c1e5fb2e8c..e8790f66da 100644 --- a/proc-macros/src/helpers.rs +++ b/proc-macros/src/helpers.rs @@ -71,7 +71,8 @@ fn find_jsonrpsee_crate(http_name: &str, ws_name: &str) -> Result { diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index d55746daaf..c4bcd30d30 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -219,8 +219,8 @@ pub(crate) mod visitor; /// /// // RPC is put into a separate module to clearly show names of generated entities. /// mod rpc_impl { -/// use jsonrpsee::{proc_macros::rpc, core::async_trait, core::RpcResult, server::PendingSubscriptionSink}; -/// use jsonrpsee::types::SubscriptionResult; +/// use jsonrpsee::{proc_macros::rpc, server::PendingSubscriptionSink}; +/// use jsonrpsee::core::{async_trait, SubscriptionResult, RpcResult}; /// /// // Generate both server and client implementations, prepend all the methods with `foo_` prefix. /// #[rpc(client, server, namespace = "foo")] diff --git a/proc-macros/src/render_server.rs b/proc-macros/src/render_server.rs index 387cc13882..1540cca088 100644 --- a/proc-macros/src/render_server.rs +++ b/proc-macros/src/render_server.rs @@ -78,7 +78,7 @@ impl RpcDescription { let mut sub_sig = sub.signature.clone(); // For ergonomic reasons, the server's subscription method should return `SubscriptionResult`. - let return_ty = self.jrps_server_item(quote! { types::SubscriptionResult }); + let return_ty = self.jrps_server_item(quote! { core::SubscriptionResult }); let output: ReturnType = parse_quote! { -> #return_ty }; sub_sig.sig.output = output; @@ -320,7 +320,7 @@ impl RpcDescription { let params_fields = quote! { #(#params_fields_seq),* }; let tracing = self.jrps_server_item(quote! { tracing }); let err = self.jrps_server_item(quote! { core::Error }); - let sub_err = self.jrps_server_item(quote! { types::SubscriptionEmptyError }); + let sub_err = self.jrps_server_item(quote! { core::SubscriptionEmptyError }); let tokio = self.jrps_server_item(quote! { tokio }); // Code to decode sequence of parameters from a JSON array. diff --git a/proc-macros/tests/ui/correct/basic.rs b/proc-macros/tests/ui/correct/basic.rs index 4f8ef2a298..6b0dcaba39 100644 --- a/proc-macros/tests/ui/correct/basic.rs +++ b/proc-macros/tests/ui/correct/basic.rs @@ -3,10 +3,10 @@ use std::net::SocketAddr; use jsonrpsee::core::params::ArrayParams; +use jsonrpsee::core::SubscriptionResult; use jsonrpsee::core::{async_trait, client::ClientT, RpcResult}; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::ServerBuilder; -use jsonrpsee::types::SubscriptionResult; use jsonrpsee::ws_client::*; use jsonrpsee::{rpc_params, PendingSubscriptionSink}; diff --git a/proc-macros/tests/ui/correct/only_server.rs b/proc-macros/tests/ui/correct/only_server.rs index b9438f7dd8..73281e90c5 100644 --- a/proc-macros/tests/ui/correct/only_server.rs +++ b/proc-macros/tests/ui/correct/only_server.rs @@ -1,9 +1,8 @@ use std::net::SocketAddr; -use jsonrpsee::core::{async_trait, RpcResult}; +use jsonrpsee::core::{async_trait, RpcResult, SubscriptionResult}; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{PendingSubscriptionSink, ServerBuilder}; -use jsonrpsee::types::SubscriptionResult; #[rpc(server)] pub trait Rpc { diff --git a/proc-macros/tests/ui/correct/parse_angle_brackets.rs b/proc-macros/tests/ui/correct/parse_angle_brackets.rs index 92b6178d09..50e9794850 100644 --- a/proc-macros/tests/ui/correct/parse_angle_brackets.rs +++ b/proc-macros/tests/ui/correct/parse_angle_brackets.rs @@ -12,6 +12,6 @@ fn main() { // angle braces need to be accounted for manually. item = TransactionStatus, )] - async fn dummy_subscription(&self) -> jsonrpsee::types::SubscriptionResult; + async fn dummy_subscription(&self) -> jsonrpsee::core::SubscriptionResult; } } diff --git a/proc-macros/tests/ui/correct/rpc_deny_missing_docs.rs b/proc-macros/tests/ui/correct/rpc_deny_missing_docs.rs index 11113a5961..aa61896d1a 100644 --- a/proc-macros/tests/ui/correct/rpc_deny_missing_docs.rs +++ b/proc-macros/tests/ui/correct/rpc_deny_missing_docs.rs @@ -13,7 +13,7 @@ pub trait ApiWithDocumentation { /// Subscription docs. #[subscription(name = "sub", unsubscribe = "unsub", item = String)] - async fn sub(&self) -> jsonrpsee::types::SubscriptionResult; + async fn sub(&self) -> jsonrpsee::core::SubscriptionResult; } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_empty_attr.rs b/proc-macros/tests/ui/incorrect/sub/sub_empty_attr.rs index 9177c94c8a..f50218dbec 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_empty_attr.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_empty_attr.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait SubEmptyAttr { #[subscription()] - async fn sub(&self) -> jsonrpsee::types::SubscriptionResult; + async fn sub(&self) -> jsonrpsee::core::SubscriptionResult; } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_name_override.rs b/proc-macros/tests/ui/incorrect/sub/sub_name_override.rs index a71eda9a42..9ae56b0525 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_name_override.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_name_override.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait DupName { #[subscription(name = "one" => "one", unsubscribe = "unsubscribeOne", item = u8)] - async fn one(&self) -> jsonrpsee::types::SubscriptionResult; + async fn one(&self) -> jsonrpsee::core::SubscriptionResult; } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_name_override.stderr b/proc-macros/tests/ui/incorrect/sub/sub_name_override.stderr index 496eecb1fc..e7a856215c 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_name_override.stderr +++ b/proc-macros/tests/ui/incorrect/sub/sub_name_override.stderr @@ -1,5 +1,5 @@ error: "one" is already defined --> $DIR/sub_name_override.rs:7:11 | -7 | async fn one(&self) -> jsonrpsee::types::SubscriptionResult; +7 | async fn one(&self) -> jsonrpsee::core::SubscriptionResult; | ^^^ diff --git a/proc-macros/tests/ui/incorrect/sub/sub_no_item.rs b/proc-macros/tests/ui/incorrect/sub/sub_no_item.rs index b31f947fb5..0295efa4d3 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_no_item.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_no_item.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait NoSubItem { #[subscription(name = "sub")] - async fn sub(&self) -> jsonrpsee::types::SubscriptionResult; + async fn sub(&self) -> jsonrpsee::core::SubscriptionResult; } fn main() {} diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index deb142caf6..305cb6c6d0 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -30,8 +30,8 @@ use std::net::SocketAddr; use std::time::Duration; use futures::{SinkExt, StreamExt}; -use jsonrpsee::core::error::{Error, SubscriptionClosed}; use jsonrpsee::core::server::host_filtering::AllowHosts; +use jsonrpsee::core::{Error, SubscriptionClosed, SubscriptionResult}; use jsonrpsee::server::middleware::proxy_get_request::ProxyGetRequestLayer; use jsonrpsee::server::{ServerBuilder, ServerHandle}; use jsonrpsee::types::error::{ErrorObject, SUBSCRIPTION_CLOSED_WITH_ERROR}; @@ -54,9 +54,7 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) let interval = interval(Duration::from_millis(50)); let stream = IntervalStream::new(interval).map(move |_| &"hello from subscription"); - pipe_from_stream(stream, pending).await; - - Ok(()) + pipe_from_stream(stream, pending).await }) .unwrap(); @@ -65,9 +63,7 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) let interval = interval(Duration::from_millis(100)); let stream = IntervalStream::new(interval).map(move |_| 1337_usize); - pipe_from_stream(stream, pending).await; - - Ok(()) + pipe_from_stream(stream, pending).await }) .unwrap(); @@ -89,9 +85,7 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) let interval = interval(Duration::from_millis(100)); let stream = IntervalStream::new(interval).zip(wrapping_counter).map(move |(_, c)| c); - pipe_from_stream(stream, pending).await; - - Ok(()) + pipe_from_stream(stream, pending).await }, ) .unwrap(); @@ -115,9 +109,7 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) .register_subscription("subscribe_5_ints", "n", "unsubscribe_5_ints", |_, pending, _| async move { let interval = interval(Duration::from_millis(50)); let stream = IntervalStream::new(interval).zip(futures::stream::iter(1..=5)).map(|(_, c)| c); - pipe_from_stream(stream, pending).await; - - Ok(()) + pipe_from_stream(stream, pending).await }) .unwrap(); @@ -173,7 +165,7 @@ pub async fn server_with_sleeping_subscription(tx: futures::channel::mpsc::Sende let interval = interval(Duration::from_secs(60 * 60)); let stream = IntervalStream::new(interval).zip(futures::stream::iter(1..=5)).map(|(_, c)| c); - pipe_from_stream(stream, pending).await; + pipe_from_stream(stream, pending).await?; let send_back = std::sync::Arc::make_mut(&mut tx); send_back.send(()).await.unwrap(); @@ -222,38 +214,34 @@ pub fn init_logger() { .try_init(); } -async fn pipe_from_stream(mut stream: S, pending: PendingSubscriptionSink) +async fn pipe_from_stream(mut stream: S, pending: PendingSubscriptionSink) -> SubscriptionResult where S: StreamExt + Unpin, T: Serialize, { - let sink = match pending.accept().await { - Ok(s) => s, - Err(_) => return, - }; + let sink = pending.accept().await?; loop { tokio::select! { // poll the sink first. biased; - _ = sink.closed() => return, + _ = sink.closed() => return Ok(()), maybe_item = stream.next() => { let item = match maybe_item { Some(item) => item, None => { let _ = sink.close(SubscriptionClosed::Success).await; - return; + return Ok(()); } }; - let msg = sink.build_message(&item).unwrap(); + let msg = sink.build_message(&item)?; if sink.send(msg).await.is_err() { - return; + return Ok(()); } }, - } } } diff --git a/tests/tests/proc_macros.rs b/tests/tests/proc_macros.rs index c5fea932ba..02f21caef1 100644 --- a/tests/tests/proc_macros.rs +++ b/tests/tests/proc_macros.rs @@ -42,9 +42,8 @@ use jsonrpsee::ws_client::*; use serde_json::json; mod rpc_impl { - use jsonrpsee::core::{async_trait, RpcResult}; + use jsonrpsee::core::{async_trait, RpcResult, SubscriptionResult}; use jsonrpsee::proc_macros::rpc; - use jsonrpsee::types::SubscriptionResult; use jsonrpsee::PendingSubscriptionSink; #[rpc(client, server, namespace = "foo")] @@ -310,7 +309,7 @@ async fn macro_zero_copy_cow() { #[cfg(not(target_os = "macos"))] #[tokio::test] async fn multiple_blocking_calls_overlap() { - use jsonrpsee::types::EmptyServerParams; + use jsonrpsee::core::EmptyServerParams; use std::time::{Duration, Instant}; let module = RpcServerImpl.into_rpc(); diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index 62dad9a021..b6c4132c4b 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -31,10 +31,11 @@ use std::time::Duration; use futures::StreamExt; use helpers::init_logger; -use jsonrpsee::core::error::Error; +use jsonrpsee::core::error::{Error, SubscriptionEmptyError}; use jsonrpsee::core::server::rpc_module::*; +use jsonrpsee::core::EmptyServerParams; use jsonrpsee::types::error::{CallError, ErrorCode, ErrorObject, PARSE_ERROR_CODE}; -use jsonrpsee::types::{EmptyServerParams, ErrorObjectOwned, Params, SubscriptionEmptyError}; +use jsonrpsee::types::{ErrorObjectOwned, Params}; use serde::{Deserialize, Serialize}; use tokio::time::interval; use tokio_stream::wrappers::IntervalStream; diff --git a/types/src/error.rs b/types/src/error.rs index b64f677192..afd26d2d3a 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -79,52 +79,6 @@ impl<'a> fmt::Display for ErrorResponse<'a> { write!(f, "{}", serde_json::to_string(&self).expect("infallible; qed")) } } -/// The return type of the subscription's method for the rpc server implementation. -/// -/// **Note**: The error does not contain any data and is discarded on drop. -pub type SubscriptionResult = Result<(), SubscriptionEmptyError>; - -/// The error returned by the subscription's method for the rpc server implementation. -/// -/// It contains no data, and neither is the error utilized. It provides an abstraction to make the -/// API more ergonomic while handling errors that may occur during the subscription callback. -#[derive(Debug, Clone, Copy)] -pub struct SubscriptionEmptyError; - -impl From for SubscriptionEmptyError { - fn from(_: anyhow::Error) -> Self { - SubscriptionEmptyError - } -} - -impl From for SubscriptionEmptyError { - fn from(_: CallError) -> Self { - SubscriptionEmptyError - } -} - -impl<'a> From> for SubscriptionEmptyError { - fn from(_: ErrorObject<'a>) -> Self { - SubscriptionEmptyError - } -} - -impl From for SubscriptionEmptyError { - fn from(_: SubscriptionAcceptRejectError) -> Self { - SubscriptionEmptyError - } -} - -/// The error returned while accepting or rejecting a subscription. -#[derive(Debug, Copy, Clone)] -pub enum SubscriptionAcceptRejectError { - /// The method was already called. - AlreadyCalled, - /// The remote peer closed the connection or called the unsubscribe method. - RemotePeerAborted, - /// The subscription response message was too large. - MessageTooLarge, -} /// Owned variant of [`ErrorObject`]. pub type ErrorObjectOwned = ErrorObject<'static>; diff --git a/types/src/lib.rs b/types/src/lib.rs index 528482747d..b68af06059 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -43,10 +43,7 @@ pub mod response; /// JSON-RPC response error object related types. pub mod error; -pub use error::{ErrorObject, ErrorObjectOwned, ErrorResponse, SubscriptionEmptyError, SubscriptionResult}; +pub use error::{ErrorObject, ErrorObjectOwned, ErrorResponse}; pub use params::{Id, Params, ParamsSequence, SubscriptionId, TwoPointZero}; pub use request::{InvalidRequest, Notification, NotificationSer, Request, RequestSer}; pub use response::{Response, SubscriptionPayload, SubscriptionResponse}; - -/// Empty server `RpcParams` type to use while registering modules. -pub type EmptyServerParams = Vec<()>; From a9002c994cba9b294b80b7336ee3c7cc27e80db0 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 26 Jan 2023 15:31:25 +0100 Subject: [PATCH 18/60] fix nits --- benches/helpers.rs | 6 +++--- core/Cargo.toml | 1 + core/src/server/rpc_module.rs | 10 ++++++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/benches/helpers.rs b/benches/helpers.rs index b555b142b8..9940b17718 100644 --- a/benches/helpers.rs +++ b/benches/helpers.rs @@ -132,7 +132,7 @@ pub async fn http_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee:: /// Run jsonrpsee WebSocket server for benchmarks. #[cfg(not(feature = "jsonrpc-crate"))] pub async fn ws_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee::server::ServerHandle) { - use jsonrpsee::{core::server::rpc_module::SubscriptionMessage, server::ServerBuilder}; + use jsonrpsee::server::ServerBuilder; let server = ServerBuilder::default() .max_request_body_size(u32::MAX) @@ -154,8 +154,8 @@ pub async fn ws_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee::se let x = "Hello"; let sink = pending.accept().await?; - let msg = SubscriptionMessage::new(x, sink.method_name(), sink.subscription_id()).unwrap(); - sink.send(msg).await.unwrap(); + let msg = sink.build_message(&x)?; + sink.send(msg).await?; Ok(()) }, ) diff --git a/core/Cargo.toml b/core/Cargo.toml index c1c95c20a3..9e7d6eb7c2 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -44,6 +44,7 @@ server = [ "rand", "tokio/rt", "tokio/sync", + "tokio/macros", ] client = ["futures-util/sink", "futures-channel/sink"] async-client = [ diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index b1d793d41f..2e26a74971 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -734,7 +734,7 @@ impl RpcModule { tokio::spawn(async move { if let Err(e) = call_fut.await { - tracing::warn!("Subscribe call `{subscribe_method_name}` canceled because `{e:?}` failed"); + tracing::warn!("Subscribe call `{subscribe_method_name}` closed"); } }); @@ -792,6 +792,7 @@ impl IsUnsubscribed { /// Wrapper over [`tokio::sync::mpsc::Sender::closed`] /// /// # Cancel safety + /// /// This method is cancel safe. Once the channel is closed, /// it stays closed forever and all future calls to closed will return immediately. async fn unsubscribed(&self) { @@ -943,7 +944,12 @@ impl SubscriptionSink { /// /// You need to call this method prior to send out a subscription notification. pub fn build_message(&self, result: &T) -> Result { - SubscriptionMessage::new(result, self.method, self.uniq_sub.sub_id.clone()) + serde_json::to_string(&SubscriptionResponse::new( + self.method.into(), + SubscriptionPayload { subscription: self.uniq_sub.sub_id.clone(), result }, + )) + .map(|json| SubscriptionMessage(json)) + .map_err(Into::into) } fn build_error_message(&self, error: &T) -> Result { From f3d19a530196997d71cb34cb97bbca3424f9cb99 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 26 Jan 2023 16:05:31 +0100 Subject: [PATCH 19/60] improve docs --- core/src/server/rpc_module.rs | 8 +++-- examples/examples/ws.rs | 2 +- examples/examples/ws_pubsub_broadcast.rs | 2 +- examples/examples/ws_pubsub_with_params.rs | 2 +- server/src/server.rs | 39 +++++++++++++++------- server/src/transport/ws.rs | 4 +-- 6 files changed, 38 insertions(+), 19 deletions(-) diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 2e26a74971..6ddcc366cb 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -730,10 +730,14 @@ impl RpcModule { subscribe: tx, }; - let call_fut = callback(params.into_owned(), sink, ctx.clone()); + // The subscription callback is a future from the subscription + // definition and not the as same when the subscription call has been completed. + // + // This runs until the subscription callback has completed. + let sub_fut = callback(params.into_owned(), sink, ctx.clone()); tokio::spawn(async move { - if let Err(e) = call_fut.await { + if sub_fut.await.is_err() { tracing::warn!("Subscribe call `{subscribe_method_name}` closed"); } }); diff --git a/examples/examples/ws.rs b/examples/examples/ws.rs index dc119c1c04..40079655de 100644 --- a/examples/examples/ws.rs +++ b/examples/examples/ws.rs @@ -50,7 +50,7 @@ async fn main() -> anyhow::Result<()> { } async fn run_server() -> anyhow::Result { - let server = ServerBuilder::default().set_buffer_size(2).build("127.0.0.1:9944").await?; + let server = ServerBuilder::default().set_backpressure_buffer_capacity(2).build("127.0.0.1:9944").await?; let mut module = RpcModule::new(()); module.register_method("say_hello", |_, _| Ok("a".repeat(1024)))?; let addr = server.local_addr()?; diff --git a/examples/examples/ws_pubsub_broadcast.rs b/examples/examples/ws_pubsub_broadcast.rs index 96ad824403..3f2f9d2bd8 100644 --- a/examples/examples/ws_pubsub_broadcast.rs +++ b/examples/examples/ws_pubsub_broadcast.rs @@ -69,7 +69,7 @@ async fn main() -> anyhow::Result<()> { async fn run_server() -> anyhow::Result { // let's configure the server only hold 5 messages in memory. - let server = ServerBuilder::default().set_buffer_size(5).build("127.0.0.1:0").await?; + let server = ServerBuilder::default().set_backpressure_buffer_capacity(5).build("127.0.0.1:0").await?; let (tx, _rx) = broadcast::channel::(16); let mut module = RpcModule::new(tx.clone()); diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index ba1ee6650b..22ad7a8f69 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -64,7 +64,7 @@ async fn main() -> anyhow::Result<()> { async fn run_server() -> anyhow::Result { const LETTERS: &str = "abcdefghijklmnopqrstuvxyz"; - let server = ServerBuilder::default().set_buffer_size(10).build("127.0.0.1:9944").await?; + let server = ServerBuilder::default().set_backpressure_buffer_capacity(10).build("127.0.0.1:9944").await?; let mut module = RpcModule::new(()); module .register_subscription("sub_one_param", "sub_one_param", "unsub_one_param", |params, pending, _| async move { diff --git a/server/src/server.rs b/server/src/server.rs index 311cbe6060..2f7605a94d 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -150,7 +150,7 @@ where max_connections: self.cfg.max_connections, enable_http: self.cfg.enable_http, enable_ws: self.cfg.enable_ws, - buffer_capacity: self.cfg.buffer_capacity, + backpressure_buffer_capacity: self.cfg.backpressure_buffer_capacity, }; process_connection(&self.service_builder, &connection_guard, data, socket, &mut connections); id = id.wrapping_add(1); @@ -192,7 +192,7 @@ struct Settings { /// Enable WS. enable_ws: bool, /// Number of messages that server is allowed `buffer` until backpressure kicks in. - buffer_capacity: u32, + backpressure_buffer_capacity: u32, } impl Default for Settings { @@ -208,7 +208,7 @@ impl Default for Settings { ping_interval: Duration::from_secs(60), enable_http: true, enable_ws: true, - buffer_capacity: 1024, + backpressure_buffer_capacity: 1024, } } } @@ -422,11 +422,26 @@ impl Builder { self } - /// Configure the max number of messages that can be buffered + /// The server enforces backpressure which means that + /// `n` messages can be buffered and if the client + /// can't keep with the server. /// - /// If this limit is exceeded the connection will be closed. - pub fn set_buffer_size(mut self, c: u32) -> Self { - self.settings.buffer_capacity = c; + /// This `capacity` is applied per connection and + /// applies globally on the connection which implies + /// all JSON-RPC messages. + /// + /// For example if a subscription produces plenty of new items + /// and the client can't keep up then no new messages are handled. + /// + /// If this limit is exceeded the server will "back-off" + /// and only accept ones the client reads pending messages. + /// + /// # Panics + /// + /// Panics if the buffer capacity is 0. + /// + pub fn set_backpressure_buffer_capacity(mut self, c: u32) -> Self { + self.settings.backpressure_buffer_capacity = c; self } @@ -572,8 +587,8 @@ pub(crate) struct ServiceData { pub(crate) enable_http: bool, /// Enable WS. pub(crate) enable_ws: bool, - /// Bounded channel capacity. - pub(crate) buffer_capacity: u32, + /// Number of messages that server is allowed `buffer` until backpressure kicks in. + pub(crate) backpressure_buffer_capacity: u32, } /// JsonRPSee service compatible with `tower`. @@ -760,8 +775,8 @@ struct ProcessConnection { enable_http: bool, /// Allow JSON-RPC WS request and WS upgrade requests. enable_ws: bool, - /// ... - buffer_capacity: u32, + /// Number of messages that server is allowed `buffer` until backpressure kicks in. + backpressure_buffer_capacity: u32, } #[instrument(name = "connection", skip_all, fields(remote_addr = %cfg.remote_addr, conn_id = %cfg.conn_id), level = "INFO")] @@ -819,7 +834,7 @@ fn process_connection<'a, L: Logger, B, U>( conn: Arc::new(conn), enable_http: cfg.enable_http, enable_ws: cfg.enable_ws, - buffer_capacity: cfg.buffer_capacity, + backpressure_buffer_capacity: cfg.backpressure_buffer_capacity, }, }; diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 4d03ad3946..d619e9df93 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -242,12 +242,12 @@ pub(crate) async fn background_task( conn_id, logger, remote_addr, - buffer_capacity, + backpressure_buffer_capacity, conn, .. } = svc; - let (tx, rx) = mpsc::channel::(buffer_capacity as usize); + let (tx, rx) = mpsc::channel::(backpressure_buffer_capacity as usize); let (conn_tx, conn_rx) = oneshot::channel(); let sink = MethodSink::new_with_limit(tx, max_response_body_size, max_log_length); From d6c09960d8fe81f6ec7bfbbef1f437de5f7dee1d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 26 Jan 2023 21:49:31 +0100 Subject: [PATCH 20/60] add pipe_from_stream APIs back --- core/src/server/helpers.rs | 8 +- core/src/server/rpc_module.rs | 183 +++++++++++++++++---- examples/examples/ws_pubsub_with_params.rs | 30 +--- proc-macros/src/lib.rs | 12 +- server/src/tests/helpers.rs | 3 +- server/src/transport/ws.rs | 24 ++- test-utils/src/mocks.rs | 8 +- 7 files changed, 186 insertions(+), 82 deletions(-) diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index 17a974e9ce..06afef1b81 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -125,14 +125,18 @@ impl MethodSink { /// /// Returns the message if the send fails such that either can be thrown away or re-sent later. pub fn try_send(&mut self, msg: String) -> Result<(), TrySendError> { + // TODO: hack to make debugging easier + self.tx.try_send(msg.clone())?; tx_log_from_str(&msg, self.max_log_length); - self.tx.try_send(msg).map_err(Into::into) + Ok(()) } /// Async send which will wait until there is space in channel buffer or that the subscription is disconnected. pub async fn send(&self, msg: String) -> Result<(), DisconnectError> { + // TODO: hack to make debugging easier + self.tx.send(msg.clone()).await?; tx_log_from_str(&msg, self.max_log_length); - self.tx.send(msg).await.map_err(Into::into) + Ok(()) } /// Waits for channel capacity. Once capacity to send one message is available, it is reserved for the caller. diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 6ddcc366cb..9cd11d01b9 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -34,10 +34,11 @@ use crate::error::{Error, SubscriptionAcceptRejectError}; use crate::id_providers::RandomIntegerIdProvider; use crate::server::helpers::MethodSink; use crate::traits::{IdProvider, ToRpcParams}; -use crate::SubscriptionResult; +use crate::{SubscriptionClosed, SubscriptionResult}; use futures_util::future::Either; use futures_util::{future::BoxFuture, FutureExt}; -use jsonrpsee_types::error::{CallError, ErrorCode, ErrorObject, ErrorObjectOwned}; +use futures_util::{Stream, StreamExt, TryStream, TryStreamExt}; +use jsonrpsee_types::error::{CallError, ErrorCode, ErrorObject, ErrorObjectOwned, SUBSCRIPTION_CLOSED_WITH_ERROR}; use jsonrpsee_types::response::{SubscriptionError, SubscriptionPayloadError}; use jsonrpsee_types::{ ErrorResponse, Id, Params, Request, Response, SubscriptionId as RpcSubscriptionId, SubscriptionPayload, @@ -146,20 +147,6 @@ type Subscribers = Arc(result: T, method: &str, sub_id: RpcSubscriptionId) -> Result { - serde_json::to_string(&SubscriptionResponse::new( - method.into(), - SubscriptionPayload { subscription: sub_id, result }, - )) - .map(|json| SubscriptionMessage(json)) - .map_err(Into::into) - } -} - /// Represent a unique subscription entry based on [`RpcSubscriptionId`] and [`ConnectionId`]. #[derive(Clone, Debug, PartialEq, Eq, Hash)] struct SubscriptionKey { @@ -362,11 +349,11 @@ impl Methods { /// let mut module = RpcModule::new(()); /// /// module.register_subscription("hi", "hi", "goodbye", |_, pending, _| async move { - /// let sink = pending.accept().await?; - /// let msg = sink.build_message(&"one answer").unwrap(); - /// sink.send(msg).await.unwrap(); + /// let sink = pending.accept().await?; + /// let msg = sink.build_message(&"one answer").unwrap(); + /// sink.send(msg).await.unwrap(); /// - /// Ok(()) + /// Ok(()) /// }).unwrap(); /// /// let (resp, mut stream) = module.raw_json_request(r#"{"jsonrpc":"2.0","method":"hi","id":0}"#).await.unwrap(); @@ -434,10 +421,10 @@ impl Methods { /// /// let mut module = RpcModule::new(()); /// module.register_subscription("hi", "hi", "goodbye", |_, pending, _| async move { - /// let sink = pending.accept().await?; + /// let sink = pending.accept().await?; /// - /// let msg = sink.build_message(&"one answer").unwrap(); - /// sink.send(msg).await.unwrap(); + /// let msg = sink.build_message(&"one answer")?; + /// sink.send(msg).await?; /// Ok(()) /// /// }).unwrap(); @@ -641,14 +628,14 @@ impl RpcModule { /// /// let mut ctx = RpcModule::new(99_usize); /// ctx.register_subscription("sub", "notif_name", "unsub", |params, pending, ctx| async move { - /// let x = params.one::()?; + /// let x = params.one::()?; /// - /// // mark the subscription is accepted after the params has been parsed successful. - /// let sink = pending.accept().await?; + /// // mark the subscription is accepted after the params has been parsed successful. + /// let sink = pending.accept().await?; /// /// let sum = x + (*ctx); - /// let msg = sink.build_message(&sum).unwrap(); - /// sink.send(msg).await.unwrap(); + /// let msg = sink.build_message(&sum)?; + /// sink.send(msg).await?; /// /// Ok(()) /// }); @@ -800,7 +787,7 @@ impl IsUnsubscribed { /// This method is cancel safe. Once the channel is closed, /// it stays closed forever and all future calls to closed will return immediately. async fn unsubscribed(&self) { - _ = self.0.closed().await; + self.0.closed().await; } } @@ -900,12 +887,16 @@ impl SubscriptionSink { self.method } - /// Send a message back to the subscribers asyncronously. + /// Send out a response on the subscription and wait until there is capacity. /// /// /// Returns /// - `Ok(())` if the message could be sent. /// - `Err(err)` if the connection or subscription was closed. + /// + /// # Cancel safety + /// + /// This method is cancel-safe and dropping a future loses its spot in the waiting queue. pub async fn send(&self, msg: SubscriptionMessage) -> Result<(), DisconnectError> { // Only possible to trigger when the connection is dropped. if self.is_closed() { @@ -916,7 +907,7 @@ impl SubscriptionSink { } /// Attempts to immediately send out the message as JSON string to the subscribers but fails if the - /// channel is full, that the connection is closed or the subscription was not explicitly accepted. + /// channel is full or the connection/subscription is closed /// /// /// This differs from [`SubscriptionSink::send`] as it will until there is capacity @@ -952,16 +943,142 @@ impl SubscriptionSink { self.method.into(), SubscriptionPayload { subscription: self.uniq_sub.sub_id.clone(), result }, )) - .map(|json| SubscriptionMessage(json)) + .map(SubscriptionMessage) .map_err(Into::into) } + /// Reads data from the `stream` and sends back data on the subscription + /// when items gets produced by the stream. + /// + /// The underlying sink is a bounded channel which may not have room for new items + /// if the client can't keep up with all the messages. Thus, you need to provide a closure + /// with a policy what to then the underlying channel has not space to "buffer" more messages. + /// + /// The underlying stream must produce `Result values, see [`futures_util::TryStream`] for further information. + /// + /// Returns `Ok(())` if the stream or connection was terminated. + /// Returns `Err(_)` immediately if the underlying stream returns an error or if an item from the stream could not be serialized. + /// + /// # Examples + /// + /// ```no_run + /// + /// use jsonrpsee_core::server::rpc_module::RpcModule; + /// use jsonrpsee_core::error::{Error, SubscriptionClosed}; + /// use jsonrpsee_types::ErrorObjectOwned; + /// use anyhow::anyhow; + /// + /// let mut m = RpcModule::new(()); + /// m.register_subscription("sub", "_", "unsub", |params, pending, _| async move { + /// let mut sink = pending.accept().await?; + /// + /// let stream = futures_util::stream::iter(vec![Ok(1_u32), Ok(2), Err("error on the stream")]); + /// // This will return send `[Ok(1_u32), Ok(2_u32), Err(Error::SubscriptionClosed))]` to the subscriber + /// // because after the `Err(_)` then the stream is terminated. + /// let stream = futures_util::stream::iter(vec![Ok(1_u32), Ok(2), Err("error on the stream")]); + /// + /// // jsonrpsee doesn't send an error notification unless `close` is explicitly called. + /// // If we pipe messages to the sink, we can inspect why it ended: + /// match sink.pipe_from_try_stream(|last, next| last.unwrap_or(next), stream).await { + /// SubscriptionClosed::Success => { + /// let err_obj: ErrorObjectOwned = SubscriptionClosed::Success.into(); + /// sink.close(err_obj).await; + /// } + /// // we don't want to send close reason when the client is unsubscribed or disconnected. + /// SubscriptionClosed::RemotePeerAborted => (), + /// SubscriptionClosed::Failed(e) => { + /// sink.close(e).await; + /// } + /// } + /// Ok(()) + /// }); + /// ``` + pub async fn pipe_from_try_stream(&mut self, f: F, mut stream: S) -> SubscriptionClosed + where + F: Fn(Option, SubscriptionMessage) -> SubscriptionMessage, + S: TryStream + Unpin, + T: Serialize, + E: std::fmt::Display, + { + fn err(err: impl std::fmt::Display) -> SubscriptionClosed { + let err = ErrorObject::owned(SUBSCRIPTION_CLOSED_WITH_ERROR, err.to_string(), None::<()>); + SubscriptionClosed::Failed(err) + } + + let mut last = None; + + loop { + tokio::select! { + // add priority for the closed future + // if that fires there's no point reading the stream + biased; + _ = self.closed() => { + break SubscriptionClosed::RemotePeerAborted + } + + item = stream.try_next() => { + let item = match item { + Ok(Some(item)) => item, + Ok(None) => break SubscriptionClosed::Success, + Err(e) => break err(e), + }; + + let next = match self.build_message(&item) { + Ok(msg) => msg, + Err(e) => break err(e), + }; + + let msg = f(last.take(), next); + + match self.try_send(msg) { + Ok(_) => (), + Err(TrySendError::Closed(_)) => break SubscriptionClosed::RemotePeerAborted, + Err(TrySendError::Full(m)) => { + tracing::debug!("Failed to send message the buffer is full"); + last = Some(m); + } + } + }, + } + } + } + + /// Similar to [`SubscriptionSink::pipe_from_try_stream`] but it doesn't require the stream return `Result`. + /// + /// Warning: it's possible to pass in a stream that returns `Result` if `Result: Serialize` is satisfied + /// but it won't cancel the stream when an error occurs. If you want the stream to be canceled when an + /// error occurs use [`SubscriptionSink::pipe_from_try_stream`] instead. + /// + /// # Examples + /// + /// ```no_run + /// + /// use jsonrpsee_core::server::rpc_module::RpcModule; + /// + /// let mut m = RpcModule::new(()); + /// m.register_subscription("sub", "_", "unsub", |params, pending, _| async move { + /// let stream = futures_util::stream::iter(vec![1_usize, 2, 3]); + /// + /// let mut sink = pending.accept().await?; + /// sink.pipe_from_stream(|_last, next| next, stream).await; + /// Ok(()) + /// }); + /// ``` + pub async fn pipe_from_stream(&mut self, f: F, stream: S) -> SubscriptionClosed + where + F: Fn(Option, SubscriptionMessage) -> SubscriptionMessage, + S: Stream + Unpin, + T: Serialize, + { + self.pipe_from_try_stream::<_, _, _, Error>(f, stream.map(|item| Ok(item))).await + } + fn build_error_message(&self, error: &T) -> Result { serde_json::to_string(&SubscriptionError::new( self.method.into(), SubscriptionPayloadError { subscription: self.uniq_sub.sub_id.clone(), error }, )) - .map(|json| SubscriptionMessage(json)) + .map(SubscriptionMessage) .map_err(Into::into) } diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index 22ad7a8f69..6d0305b6ad 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -29,7 +29,6 @@ use std::time::Duration; use futures::StreamExt; use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; -use jsonrpsee::core::server::rpc_module::TrySendError; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, ServerBuilder}; use jsonrpsee::types::ErrorObjectOwned; @@ -80,45 +79,26 @@ async fn run_server() -> anyhow::Result { let item = LETTERS.chars().nth(idx); let interval = interval(Duration::from_millis(200)); - let mut stream = IntervalStream::new(interval).map(move |_| item); + let stream = IntervalStream::new(interval).map(move |_| item); let mut sink = pending.accept().await.unwrap(); - while let Some(item) = stream.next().await { - let notif = sink.build_message(&item).unwrap(); - if let Err(e) = sink.try_send(notif) { - tracing::info!("ignoring to send notif: {:?}", e); - } - } + sink.pipe_from_stream(|_last, next| next, stream).await; Ok(()) }) .unwrap(); module .register_subscription("sub_params_two", "params_two", "unsub_params_two", |params, pending, _| async move { - let (one, two) = params.parse::<(usize, usize)>().unwrap(); + let (one, two) = params.parse::<(usize, usize)>()?; let item = &LETTERS[one..two]; - let interval = interval(Duration::from_millis(200)); - let mut stream = IntervalStream::new(interval).map(move |_| item); + let stream = IntervalStream::new(interval).map(move |_| item); let mut sink = pending.accept().await?; - while let Some(item) = stream.next().await { - let notif = sink.build_message(&item)?; - match sink.try_send(notif) { - Ok(_) => (), - Err(TrySendError::Closed(m)) => { - tracing::warn!("Subscription is closed; failed to send msg: {:?}", m); - return Ok(()); - } - Err(TrySendError::Full(m)) => { - // you could buffer the message if you want to and try re-send them. - tracing::info!("channel is full; dropping message: {:?}", m); - } - }; - } + sink.pipe_from_stream(|_last, next| next, stream).await; Ok(()) }) diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index c4bcd30d30..744b3fe82c 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -293,20 +293,20 @@ pub(crate) mod visitor; /// // The stream API can be used to pipe items from the underlying stream /// // as subscription responses. /// async fn sub_override_notif_method(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { -/// let sink = pending.accept().await?; +/// let mut sink = pending.accept().await?; /// -/// //let stream = futures_util::stream::iter(["one", "two", "three"]); -/// //sink.pipe_from_stream(stream).await; +/// let stream = futures_util::stream::iter(["one", "two", "three"]); +/// sink.pipe_from_stream(|_last, next| next, stream).await; /// /// Ok(()) /// } /// /// // Send out two values on the subscription. /// async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { -/// let sink = pending.accept().await?; +/// let sink = pending.accept().await?; /// -/// let msg1 = sink.build_message(&"Response_A").unwrap(); -/// let msg2 = sink.build_message(&"Response_B").unwrap(); +/// let msg1 = sink.build_message(&"Response_A").unwrap(); +/// let msg2 = sink.build_message(&"Response_B").unwrap(); /// /// sink.send(msg1).await.unwrap(); /// sink.send(msg2).await.unwrap(); diff --git a/server/src/tests/helpers.rs b/server/src/tests/helpers.rs index 26c1f26293..ccad473cd5 100644 --- a/server/src/tests/helpers.rs +++ b/server/src/tests/helpers.rs @@ -83,7 +83,7 @@ pub(crate) async fn server_with_handles() -> (SocketAddr, ServerHandle) { .unwrap(); module .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, pending, _| async move { - let sink = pending.accept().await.unwrap(); + let sink = pending.accept().await?; loop { let _ = &sink; @@ -91,6 +91,7 @@ pub(crate) async fn server_with_handles() -> (SocketAddr, ServerHandle) { } }) .unwrap(); + module.register_method("notif", |_, _| Ok("")).unwrap(); module .register_method("should_err", |_, ctx| { diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index d619e9df93..672ef57b5f 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -31,10 +31,8 @@ pub(crate) type Sender = soketto::Sender>>> pub(crate) type Receiver = soketto::Receiver>>>; pub(crate) async fn send_message(sender: &mut Sender, response: String) -> Result<(), Error> { - tracing::trace!("attempting to send: {}", response); - sender.send_text_owned(response.clone()).await?; + sender.send_text_owned(response).await?; sender.flush().await?; - tracing::trace!("sent msg: {}", response); Ok(()) } @@ -262,14 +260,17 @@ pub(crate) async fn background_task( let result = loop { data.clear(); - // Wait until the is space in the bounded channel and - // don't poll the underlying socket until a spot has been reserved. + let sink_permit_fut = sink.reserve(); + + tokio::pin!(sink_permit_fut); + + // Wait until there is a slot in the bounded channel which means that + // the underlying TCP socket won't be read. // // This will force the client to read socket on the other side // otherwise the socket will not be read again. - let sink_permit = match sink.reserve().await { - Ok(p) => p, - // reserve only fails if the channel is disconnected. + let sink_permit = match method_executors.select_with(Monitored::new(sink_permit_fut, &stop_handle)).await { + Ok(permit) => permit, Err(_) => break Ok(()), }; @@ -428,9 +429,6 @@ async fn send_task( ping_interval: Duration, conn_closed: oneshot::Receiver<()>, ) { - // fake that no messages were read. - //future::pending::<()>().await; - // Interval to send out continuously `pings`. let ping_interval = IntervalStream::new(tokio::time::interval(ping_interval)); let stopped = stop_handle.shutdown(); @@ -451,7 +449,7 @@ async fn send_task( Either::Left((Some(response), not_ready)) => { // If websocket message send fail then terminate the connection. if let Err(err) = send_message(&mut ws_sender, response).await { - tracing::error!("WS transport error: send failed: {}", err); + tracing::debug!("WS transport error: send failed: {}", err); break; } @@ -467,7 +465,7 @@ async fn send_task( // Handle timer intervals. Either::Right((Either::Left((_, stop)), next_rx)) => { if let Err(err) = send_ping(&mut ws_sender).await { - tracing::error!("WS transport error: send ping failed: {}", err); + tracing::debug!("WS transport error: send ping failed: {}", err); break; } rx_item = next_rx; diff --git a/test-utils/src/mocks.rs b/test-utils/src/mocks.rs index 3a545ac8c6..c7fb001639 100644 --- a/test-utils/src/mocks.rs +++ b/test-utils/src/mocks.rs @@ -118,13 +118,17 @@ impl WebSocketTestClient { } pub async fn send_request_text(&mut self, msg: impl AsRef) -> Result { - self.tx.send_text(msg).await?; - self.tx.flush().await?; + self.send(msg).await?; let mut data = Vec::new(); self.rx.receive_data(&mut data).await?; String::from_utf8(data).map_err(Into::into) } + pub async fn send(&mut self, msg: impl AsRef) -> Result<(), Error> { + self.tx.send_text(msg).await?; + self.tx.flush().await.map_err(Into::into) + } + pub async fn send_request_binary(&mut self, msg: &[u8]) -> Result { self.tx.send_binary(msg).await?; self.tx.flush().await?; From ec35e8f9bc0d6e32400dcdcfc0699c5c845dd0bd Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 27 Jan 2023 21:20:43 +0100 Subject: [PATCH 21/60] cleanup --- core/src/server/helpers.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index 06afef1b81..817fa83ed1 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -125,17 +125,17 @@ impl MethodSink { /// /// Returns the message if the send fails such that either can be thrown away or re-sent later. pub fn try_send(&mut self, msg: String) -> Result<(), TrySendError> { - // TODO: hack to make debugging easier - self.tx.try_send(msg.clone())?; tx_log_from_str(&msg, self.max_log_length); + self.tx.try_send(msg)?; + Ok(()) } /// Async send which will wait until there is space in channel buffer or that the subscription is disconnected. pub async fn send(&self, msg: String) -> Result<(), DisconnectError> { - // TODO: hack to make debugging easier - self.tx.send(msg.clone()).await?; tx_log_from_str(&msg, self.max_log_length); + self.tx.send(msg.clone()).await?; + Ok(()) } From c6efb4306b9fa2f7c075d6d421409ddaed9043f7 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 27 Jan 2023 21:25:27 +0100 Subject: [PATCH 22/60] Update core/src/server/helpers.rs --- core/src/server/helpers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index 817fa83ed1..7ce5d27e64 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -134,7 +134,7 @@ impl MethodSink { /// Async send which will wait until there is space in channel buffer or that the subscription is disconnected. pub async fn send(&self, msg: String) -> Result<(), DisconnectError> { tx_log_from_str(&msg, self.max_log_length); - self.tx.send(msg.clone()).await?; + self.tx.send(msg).await?; Ok(()) } From a252c07567e81cd8fcf4dd214ce99cfd7d6be42e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 27 Jan 2023 21:27:48 +0100 Subject: [PATCH 23/60] Update server/src/server.rs --- server/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server.rs b/server/src/server.rs index 2f7605a94d..9c8c1a4f0d 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -191,7 +191,7 @@ struct Settings { enable_http: bool, /// Enable WS. enable_ws: bool, - /// Number of messages that server is allowed `buffer` until backpressure kicks in. + /// Number of messages that server is allowed to `buffer` until backpressure kicks in. backpressure_buffer_capacity: u32, } From 78298cd94c185cc517bb263d7b82f73eeacb9ecb Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 27 Jan 2023 21:58:03 +0100 Subject: [PATCH 24/60] more cleanup --- examples/examples/ws_pubsub_with_params.rs | 6 +- tests/tests/helpers.rs | 117 ++++++++----- tests/tests/integration_tests.rs | 181 +++++++-------------- tests/tests/rpc_module.rs | 12 +- 4 files changed, 140 insertions(+), 176 deletions(-) diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index 6d0305b6ad..4d0a64d66a 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -67,8 +67,7 @@ async fn run_server() -> anyhow::Result { let mut module = RpcModule::new(()); module .register_subscription("sub_one_param", "sub_one_param", "unsub_one_param", |params, pending, _| async move { - let params = params.into_owned(); - + // we are doing this verbose way to get a customized reject error on the subscription. let idx = match params.one::() { Ok(p) => p, Err(e) => { @@ -76,12 +75,13 @@ async fn run_server() -> anyhow::Result { return Ok(()); } }; + let item = LETTERS.chars().nth(idx); let interval = interval(Duration::from_millis(200)); let stream = IntervalStream::new(interval).map(move |_| item); - let mut sink = pending.accept().await.unwrap(); + let mut sink = pending.accept().await?; sink.pipe_from_stream(|_last, next| next, stream).await; diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index ad75c022ac..42b621f5e0 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -31,13 +31,12 @@ use std::time::Duration; use futures::{SinkExt, StreamExt}; use jsonrpsee::core::server::host_filtering::AllowHosts; -use jsonrpsee::core::{Error, SubscriptionClosed, SubscriptionResult}; +use jsonrpsee::core::{Error, SubscriptionClosed}; use jsonrpsee::server::middleware::proxy_get_request::ProxyGetRequestLayer; use jsonrpsee::server::{ServerBuilder, ServerHandle}; use jsonrpsee::types::error::{ErrorObject, SUBSCRIPTION_CLOSED_WITH_ERROR}; use jsonrpsee::types::ErrorObjectOwned; -use jsonrpsee::{PendingSubscriptionSink, RpcModule}; -use serde::Serialize; +use jsonrpsee::RpcModule; use tokio::time::interval; use tokio_stream::wrappers::IntervalStream; use tower_http::cors::CorsLayer; @@ -50,11 +49,14 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) module.register_method("say_hello", |_, _| Ok("hello")).unwrap(); module - .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, pending, _| async { + .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, pending, _| async move { let interval = interval(Duration::from_millis(50)); let stream = IntervalStream::new(interval).map(move |_| &"hello from subscription"); - pipe_from_stream(stream, pending).await + let mut sink = pending.accept().await?; + sink.pipe_from_stream(|_, next| next, stream).await; + + Ok(()) }) .unwrap(); @@ -63,7 +65,10 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) let interval = interval(Duration::from_millis(100)); let stream = IntervalStream::new(interval).map(move |_| 1337_usize); - pipe_from_stream(stream, pending).await + let mut sink = pending.accept().await?; + sink.pipe_from_stream(|_, next| next, stream).await; + + Ok(()) }) .unwrap(); @@ -85,7 +90,10 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) let interval = interval(Duration::from_millis(100)); let stream = IntervalStream::new(interval).zip(wrapping_counter).map(move |(_, c)| c); - pipe_from_stream(stream, pending).await + let mut sink = pending.accept().await?; + sink.pipe_from_stream(|_, next| next, stream).await; + + Ok(()) }, ) .unwrap(); @@ -109,10 +117,67 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) .register_subscription("subscribe_5_ints", "n", "unsubscribe_5_ints", |_, pending, _| async move { let interval = interval(Duration::from_millis(50)); let stream = IntervalStream::new(interval).zip(futures::stream::iter(1..=5)).map(|(_, c)| c); - pipe_from_stream(stream, pending).await + + let mut sink = pending.accept().await?; + match sink.pipe_from_stream(|_, next| next, stream).await { + SubscriptionClosed::Success => { + sink.close(SubscriptionClosed::Success).await; + } + _ => unreachable!(), + } + + Ok(()) }) .unwrap(); + module + .register_subscription("can_reuse_subscription", "n", "u_can_reuse_subscription", |_, pending, _| async move { + let stream1 = IntervalStream::new(interval(Duration::from_millis(50))) + .zip(futures::stream::iter(1..=5)) + .map(|(_, c)| c); + let stream2 = IntervalStream::new(interval(Duration::from_millis(50))) + .zip(futures::stream::iter(6..=10)) + .map(|(_, c)| c); + + let mut sink = pending.accept().await?; + let result = sink.pipe_from_stream(|_, next| next, stream1).await; + assert!(matches!(result, SubscriptionClosed::Success)); + + match sink.pipe_from_stream(|_, next| next, stream2).await { + SubscriptionClosed::Success => { + sink.close(SubscriptionClosed::Success).await; + } + _ => unreachable!(), + } + + Ok(()) + }) + .unwrap(); + + module + .register_subscription( + "subscribe_with_err_on_stream", + "n", + "unsubscribe_with_err_on_stream", + move |_, pending, _| async { + let err: &'static str = "error on the stream"; + + // Create stream that produce an error which will cancel the subscription. + let stream = futures::stream::iter(vec![Ok(1_u32), Err(err), Ok(2), Ok(3)]); + + let mut sink = pending.accept().await?; + match sink.pipe_from_try_stream(|_, next| next, stream).await { + SubscriptionClosed::Failed(e) => { + sink.close(e).await; + } + _ => unreachable!(), + }; + + Ok(()) + }, + ) + .unwrap(); + let addr = server.local_addr().unwrap(); let server_handle = server.start(module).unwrap(); @@ -165,7 +230,9 @@ pub async fn server_with_sleeping_subscription(tx: futures::channel::mpsc::Sende let interval = interval(Duration::from_secs(60 * 60)); let stream = IntervalStream::new(interval).zip(futures::stream::iter(1..=5)).map(|(_, c)| c); - pipe_from_stream(stream, pending).await?; + let mut sink = pending.accept().await?; + sink.pipe_from_stream(|_, next| next, stream).await; + let send_back = std::sync::Arc::make_mut(&mut tx); send_back.send(()).await.unwrap(); @@ -213,35 +280,3 @@ pub fn init_logger() { .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .try_init(); } - -async fn pipe_from_stream(mut stream: S, pending: PendingSubscriptionSink) -> SubscriptionResult -where - S: StreamExt + Unpin, - T: Serialize, -{ - let sink = pending.accept().await?; - - loop { - tokio::select! { - // poll the sink first. - biased; - _ = sink.closed() => return Ok(()), - - maybe_item = stream.next() => { - let item = match maybe_item { - Some(item) => item, - None => { - let _ = sink.close(SubscriptionClosed::Success).await; - return Ok(()); - } - }; - - let msg = sink.build_message(&item)?; - - if sink.send(msg).await.is_err() { - return Ok(()); - } - }, - } - } -} diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 7f8012df35..035d97f055 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -594,6 +594,24 @@ async fn ws_server_cancels_subscriptions_on_reset_conn() { assert_eq!(rx_len, 10); } +#[tokio::test] +async fn ws_server_cancels_sub_stream_after_err() { + init_logger(); + + let addr = server_with_subscription().await; + let server_url = format!("ws://{}", addr); + + let client = WsClientBuilder::default().build(&server_url).await.unwrap(); + let mut sub: Subscription = client + .subscribe("subscribe_with_err_on_stream", rpc_params![], "unsubscribe_with_err_on_stream") + .await + .unwrap(); + + assert_eq!(sub.next().await.unwrap().unwrap(), 1); + // The server closed down the subscription with the underlying error from the stream. + assert!(sub.next().await.is_none()); +} + #[tokio::test] async fn ws_server_subscribe_with_stream() { init_logger(); @@ -626,6 +644,46 @@ async fn ws_server_subscribe_with_stream() { assert!(sub1.next().await.is_none()); } +#[tokio::test] +async fn ws_server_pipe_from_stream_should_cancel_tasks_immediately() { + init_logger(); + + let (tx, rx) = mpsc::channel(1); + let server_url = format!("ws://{}", helpers::server_with_sleeping_subscription(tx).await); + + let client = WsClientBuilder::default().build(&server_url).await.unwrap(); + let mut subs = Vec::new(); + + for _ in 0..10 { + subs.push( + client.subscribe::("subscribe_sleep", rpc_params![], "unsubscribe_sleep").await.unwrap(), + ) + } + + // This will call the `unsubscribe method`. + drop(subs); + + let rx_len = rx.take(10).fold(0, |acc, _| async move { acc + 1 }).await; + + assert_eq!(rx_len, 10); +} + +#[tokio::test] +async fn ws_server_pipe_from_stream_can_be_reused() { + init_logger(); + + let addr = server_with_subscription().await; + let client = WsClientBuilder::default().build(&format!("ws://{}", addr)).await.unwrap(); + let sub = client + .subscribe::("can_reuse_subscription", rpc_params![], "u_can_reuse_subscription") + .await + .unwrap(); + + let items = sub.fold(0, |acc, _| async move { acc + 1 }).await; + + assert_eq!(items, 10); +} + #[tokio::test] async fn ws_batch_works() { init_logger(); @@ -706,129 +764,6 @@ async fn http_batch_works() { assert_eq!(err_responses, vec![&ErrorObject::borrowed(UNKNOWN_ERROR_CODE, &"Custom error: err", None)]); } -// re-write test with backpressure. -/*#[tokio::test] -async fn ws_server_limit_subs_per_conn_works() { - use futures::StreamExt; - use jsonrpsee::types::error::{CallError, TOO_MANY_SUBSCRIPTIONS_CODE, TOO_MANY_SUBSCRIPTIONS_MSG}; - use jsonrpsee::{server::ServerBuilder, RpcModule}; - - init_logger(); - - let server = ServerBuilder::default().max_subscriptions_per_connection(10).build("127.0.0.1:0").await.unwrap(); - let server_url = format!("ws://{}", server.local_addr().unwrap()); - - let mut module = RpcModule::new(()); - - module - .register_subscription("subscribe_forever", "n", "unsubscribe_forever", |_, mut sink, _| { - tokio::spawn(async move { - let interval = interval(Duration::from_millis(50)); - let stream = IntervalStream::new(interval).map(move |_| 0_usize); - - match sink.pipe_from_stream(stream).await { - SubscriptionClosed::Success => { - sink.close(SubscriptionClosed::Success); - } - _ => unreachable!(), - }; - }); - Ok(()) - }) - .unwrap(); - let _handle = server.start(module).unwrap(); - - let c1 = WsClientBuilder::default().build(&server_url).await.unwrap(); - let c2 = WsClientBuilder::default().build(&server_url).await.unwrap(); - - let mut subs1 = Vec::new(); - let mut subs2 = Vec::new(); - - for _ in 0..10 { - subs1.push( - c1.subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever") - .await - .unwrap(), - ); - subs2.push( - c2.subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever") - .await - .unwrap(), - ); - } - - let err1 = c1.subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever").await; - let err2 = c1.subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever").await; - - let data = "\"Exceeded max limit of 10\""; - - assert!( - matches!(err1, Err(Error::Call(CallError::Custom(err))) if err.code() == TOO_MANY_SUBSCRIPTIONS_CODE && err.message() == TOO_MANY_SUBSCRIPTIONS_MSG && err.data().unwrap().get() == data) - ); - assert!( - matches!(err2, Err(Error::Call(CallError::Custom(err))) if err.code() == TOO_MANY_SUBSCRIPTIONS_CODE && err.message() == TOO_MANY_SUBSCRIPTIONS_MSG && err.data().unwrap().get() == data) - ); -} - -#[tokio::test] -async fn ws_server_unsub_methods_should_ignore_sub_limit() { - use futures::StreamExt; - use jsonrpsee::core::client::SubscriptionKind; - use jsonrpsee::{server::ServerBuilder, RpcModule}; - - init_logger(); - - let server = ServerBuilder::default().max_subscriptions_per_connection(10).build("127.0.0.1:0").await.unwrap(); - let server_url = format!("ws://{}", server.local_addr().unwrap()); - - let mut module = RpcModule::new(()); - - module - .register_subscription("subscribe_forever", "n", "unsubscribe_forever", |_, mut sink, _| { - tokio::spawn(async move { - let interval = interval(Duration::from_millis(50)); - let stream = IntervalStream::new(interval).map(move |_| 0_usize); - - match sink.pipe_from_stream(stream).await { - SubscriptionClosed::RemotePeerAborted => { - sink.close(SubscriptionClosed::RemotePeerAborted); - } - _ => unreachable!(), - }; - }); - Ok(()) - }) - .unwrap(); - let _handle = server.start(module).unwrap(); - - let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - - // Add 10 subscriptions (this should fill our subscrition limit for this connection): - let mut subs = Vec::new(); - for _ in 0..10 { - subs.push( - client - .subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever") - .await - .unwrap(), - ); - } - - // Get the ID of one of them: - let last_sub = subs.pop().unwrap(); - let last_sub_id = match last_sub.kind() { - SubscriptionKind::Subscription(id) => id.clone(), - _ => panic!("Expected a subscription Id to be present"), - }; - - // Manually call the unsubscribe function for this subscription: - let res: Result = client.request("unsubscribe_forever", rpc_params![last_sub_id]).await; - - // This should not hit any limits, and unsubscription should have worked: - assert!(res.is_ok(), "Unsubscription method was successfully called"); - assert!(res.unwrap(), "Unsubscription was successful"); -}*/ - #[tokio::test] async fn http_unsupported_methods_dont_work() { use hyper::{Body, Client, Method, Request}; diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index a7820e907b..32c20e1e12 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -346,16 +346,10 @@ async fn subscribe_unsubscribe_without_server() { module .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| async move { let interval = interval(Duration::from_millis(200)); - let mut stream = IntervalStream::new(interval).map(move |_| 1); + let stream = IntervalStream::new(interval).map(move |_| 1); + let mut sink = pending.accept().await.unwrap(); - let sink = pending.accept().await.unwrap(); - - while let Some(item) = stream.next().await { - let msg = sink.build_message(&item).unwrap(); - if sink.send(msg).await.is_err() { - return Ok(()); - } - } + sink.pipe_from_stream(|_, next| next, stream).await; Ok(()) }) .unwrap(); From 1001603e1c5e787ca233e6e0fd4a0349776f870a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 30 Jan 2023 08:20:32 +0100 Subject: [PATCH 25/60] Update core/src/server/helpers.rs --- core/src/server/helpers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index 7ce5d27e64..0b76f317f3 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -84,7 +84,7 @@ impl<'a> io::Write for &'a mut BoundedWriter { #[derive(Clone, Debug)] pub struct MethodSink { /// Channel sender. - pub tx: mpsc::Sender, + tx: mpsc::Sender, /// Max response size in bytes for a executed call. max_response_size: u32, /// Max log length. From 701ff756a29cfb8aed1aa5a7a0474af3fa14b5f5 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 30 Jan 2023 08:44:14 +0100 Subject: [PATCH 26/60] small fixes --- server/src/transport/ws.rs | 8 +++----- tests/tests/integration_tests.rs | 14 ++++---------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 672ef57b5f..d9f458e8bd 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -32,9 +32,7 @@ pub(crate) type Receiver = soketto::Receiver Result<(), Error> { sender.send_text_owned(response).await?; - sender.flush().await?; - - Ok(()) + sender.flush().await.map_err(Into::into) } pub(crate) async fn send_ping(sender: &mut Sender) -> Result<(), Error> { @@ -300,7 +298,7 @@ pub(crate) async fn background_task( break Ok(()); } MonitoredError::Selector(SokettoError::MessageTooLarge { current, maximum }) => { - tracing::warn!( + tracing::debug!( "WS transport error: request length: {} exceeded max limit: {} bytes", current, maximum @@ -312,7 +310,7 @@ pub(crate) async fn background_task( // These errors can not be gracefully handled, so just log them and terminate the connection. MonitoredError::Selector(err) => { - tracing::error!("WS transport error: {}; terminate connection: {}", err, conn_id); + tracing::debug!("WS transport error: {}; terminate connection: {}", err, conn_id); break Err(err.into()); } MonitoredError::Shutdown => { diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 035d97f055..a7403ff94d 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -40,7 +40,6 @@ use helpers::{ use hyper::http::HeaderValue; use jsonrpsee::core::client::{ClientT, IdKind, Subscription, SubscriptionClientT}; use jsonrpsee::core::params::{ArrayParams, BatchRequestBuilder}; -use jsonrpsee::core::server::rpc_module::DisconnectError; use jsonrpsee::core::{Error, JsonValue}; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::rpc_params; @@ -440,15 +439,10 @@ async fn ws_server_should_stop_subscription_after_client_drop() { |_, pending, mut tx| async move { let sink = pending.accept().await.unwrap(); let msg = sink.build_message(&1).unwrap(); - - let close_err = loop { - if let Err(DisconnectError(_msg)) = sink.send(msg.clone()).await { - break ErrorObject::borrowed(0, &"Subscription terminated successfully", None); - } - tokio::time::sleep(Duration::from_millis(100)).await; - }; + sink.send(msg).await.unwrap(); + sink.closed().await; let send_back = Arc::make_mut(&mut tx); - send_back.feed(close_err).await.unwrap(); + send_back.feed("Subscription terminated by remote peer").await.unwrap(); Ok(()) }, @@ -469,7 +463,7 @@ async fn ws_server_should_stop_subscription_after_client_drop() { let close_err = rx.next().await.unwrap(); // assert that the server received `SubscriptionClosed` after the client was dropped. - assert_eq!(close_err, ErrorObject::borrowed(0, &"Subscription terminated successfully", None)); + assert_eq!(close_err, "Subscription terminated by remote peer"); } #[tokio::test] From 93623e52b68a550a52dd3dc628b27e89918a26aa Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 2 Feb 2023 18:40:05 +0100 Subject: [PATCH 27/60] rpc module: add unit test for backpressure --- core/src/server/rpc_module.rs | 59 ++++++++++++++--------- tests/tests/proc_macros.rs | 20 +++++--- tests/tests/rpc_module.rs | 91 ++++++++++++++++++++++++++++++----- 3 files changed, 131 insertions(+), 39 deletions(-) diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 9cd11d01b9..aaa26169f7 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -321,7 +321,8 @@ impl Methods { let params = params.to_rpc_params()?; let req = Request::new(method.into(), params.as_ref().map(|p| p.as_ref()), Id::Number(0)); tracing::trace!("[Methods::call] Method: {:?}, params: {:?}", method, params); - let (resp, _, _) = self.inner_call(req).await; + let (tx, _rx) = mpsc::channel(1); + let resp = self.inner_call(req, tx).await; if resp.success { serde_json::from_str::>(&resp.result).map(|r| r.result).map_err(Into::into) @@ -335,7 +336,12 @@ impl Methods { /// Make a request (JSON-RPC method call or subscription) by using raw JSON. /// - /// Returns the raw JSON response to the call and a stream to receive notifications if the call was a subscription. + /// This is a low-level API where you might be able build "in-memory" client + /// but it's not ergonomic to use, see [`RpcModule::call`] and [`RpcModule::subscribe_bounded`] + /// for more ergonomic APIs to perform method calls or subscriptions for testing or other purposes. + /// + /// Returns the raw JSON response to the call, additional responses are sent out + /// on the the receiver connected to the sender as passed to this method. /// /// # Examples /// @@ -356,26 +362,27 @@ impl Methods { /// Ok(()) /// }).unwrap(); /// - /// let (resp, mut stream) = module.raw_json_request(r#"{"jsonrpc":"2.0","method":"hi","id":0}"#).await.unwrap(); + /// let (tx, mut rx) = tokio::sync::mpsc::channel(16); + /// let resp = module.raw_json_request(r#"{"jsonrpc":"2.0","method":"hi","id":0}"#, tx).await.unwrap(); /// let resp = serde_json::from_str::>(&resp.result).unwrap(); - /// let sub_resp = stream.recv().await.unwrap(); + /// // ignore duplicated subscription call response. + /// _ = rx.recv().await.unwrap(); + /// let sub_resp = rx.recv().await.unwrap(); /// assert_eq!( /// format!(r#"{{"jsonrpc":"2.0","method":"hi","params":{{"subscription":{},"result":"one answer"}}}}"#, resp.result), /// sub_resp /// ); /// } /// ``` - pub async fn raw_json_request(&self, request: &str) -> Result<(MethodResponse, mpsc::Receiver), Error> { + pub async fn raw_json_request(&self, request: &str, tx: mpsc::Sender) -> Result { tracing::trace!("[Methods::raw_json_request] Request: {:?}", request); let req: Request = serde_json::from_str(request)?; - let (resp, rx, _) = self.inner_call(req).await; - Ok((resp, rx)) + let resp = self.inner_call(req, tx).await; + Ok(resp) } /// Execute a callback. - async fn inner_call(&self, req: Request<'_>) -> RawRpcResponse { - let (tx_sink, mut rx_sink) = mpsc::channel(u32::MAX as usize / 2); - let sink = MethodSink::new(tx_sink); + async fn inner_call(&self, req: Request<'_>, tx: mpsc::Sender) -> MethodResponse { let id = req.id.clone(); let params = Params::new(req.params.map(|params| params.get())); @@ -385,13 +392,7 @@ impl Methods { Some(MethodKind::Async(cb)) => (cb)(id.into_owned(), params.into_owned(), 0, usize::MAX).await, Some(MethodKind::Subscription(cb)) => { let conn_state = ConnState { conn_id: 0, id_provider: &RandomIntegerIdProvider }; - let res = (cb)(id, params, sink.clone(), conn_state).await; - - // This message is not used because it's used for metrics so we discard in other to - // not read once this is used for subscriptions. - // - // The same information is part of `res` above. - let _ = rx_sink.recv().await.expect("Every call must at least produce one response; qed"); + let res = (cb)(id, params, MethodSink::new(tx), conn_state).await; match res { SubscriptionAnswered::Yes(r) => r, @@ -403,7 +404,7 @@ impl Methods { tracing::trace!("[Methods::inner_call] Method: {}, response: {:?}", req.method, response); - (response, rx_sink, sink) + response } /// Helper to create a subscription on the `RPC module` without having to spin up a server. @@ -429,19 +430,30 @@ impl Methods { /// /// }).unwrap(); /// - /// let mut sub = module.subscribe("hi", EmptyServerParams::new()).await.unwrap(); + /// let mut sub = module.subscribe_unbounded("hi", EmptyServerParams::new()).await.unwrap(); /// // In this case we ignore the subscription ID, /// let (sub_resp, _sub_id) = sub.next::().await.unwrap().unwrap(); /// assert_eq!(&sub_resp, "one answer"); /// } /// ``` - pub async fn subscribe(&self, sub_method: &str, params: impl ToRpcParams) -> Result { + pub async fn subscribe_unbounded(&self, sub_method: &str, params: impl ToRpcParams) -> Result { + self.subscribe_bounded(sub_method, params, u32::MAX as usize).await + } + + /// Similar to `RpcMethods::subscribe_unbounded` but it's a using bounded channel. + pub async fn subscribe_bounded( + &self, + sub_method: &str, + params: impl ToRpcParams, + buf_size: usize, + ) -> Result { let params = params.to_rpc_params()?; let req = Request::new(sub_method.into(), params.as_ref().map(|p| p.as_ref()), Id::Number(0)); + let (tx, mut rx) = mpsc::channel(buf_size); tracing::trace!("[Methods::subscribe] Method: {}, params: {:?}", sub_method, params); - let (response, rx, tx) = self.inner_call(req).await; + let response = self.inner_call(req, tx.clone()).await; let subscription_response = match serde_json::from_str::>(&response.result) { Ok(r) => r, @@ -452,8 +464,10 @@ impl Methods { }; let sub_id = subscription_response.result.into_owned(); + // throw away the response to the subscription call. + _ = rx.recv().await; - Ok(Subscription { sub_id, rx, tx }) + Ok(Subscription { sub_id, rx, tx: MethodSink::new(tx) }) } /// Returns an `Iterator` with all the method names registered on this server. @@ -835,6 +849,7 @@ impl PendingSubscriptionSink { let permit = self.inner.reserve().await.map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; Self::answer_subscription(permit, response, self.subscribe).await?; + tracing::info!("accepted"); if success { let (tx, rx) = mpsc::channel(1); diff --git a/tests/tests/proc_macros.rs b/tests/tests/proc_macros.rs index 02f21caef1..3a8dfdb931 100644 --- a/tests/tests/proc_macros.rs +++ b/tests/tests/proc_macros.rs @@ -209,6 +209,7 @@ mod rpc_impl { // Use generated implementations of server and client. use jsonrpsee::core::params::{ArrayParams, ObjectParams}; use rpc_impl::{RpcClient, RpcServer, RpcServerImpl}; +use tokio::sync::mpsc; pub async fn server() -> SocketAddr { let server = ServerBuilder::default().build("127.0.0.1:0").await.unwrap(); @@ -259,6 +260,8 @@ async fn macro_param_parsing() { async fn macro_optional_param_parsing() { let module = RpcServerImpl.into_rpc(); + let (tx, _) = mpsc::channel(1); + // Optional param omitted at tail let res: String = module.call("foo_optional_params", [42_u64, 70]).await.unwrap(); assert_eq!(&res, "Called with: 42, Some(70), None"); @@ -269,8 +272,8 @@ async fn macro_optional_param_parsing() { assert_eq!(&res, "Called with: 42, None, Some(70)"); // Named params using a map - let (resp, _) = module - .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_optional_params","params":{"a":22,"c":50},"id":0}"#) + let resp = module + .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_optional_params","params":{"a":22,"c":50},"id":0}"#, tx) .await .unwrap(); assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":"Called with: 22, None, Some(50)","id":0}"#); @@ -289,8 +292,13 @@ async fn macro_lifetimes_parsing() { async fn macro_zero_copy_cow() { let module = RpcServerImpl.into_rpc(); - let (resp, _) = module - .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_zero_copy_cow","params":["foo", "bar"],"id":0}"#) + let (tx, _) = mpsc::channel(1); + + let resp = module + .raw_json_request( + r#"{"jsonrpc":"2.0","method":"foo_zero_copy_cow","params":["foo", "bar"],"id":0}"#, + tx.clone(), + ) .await .unwrap(); @@ -298,8 +306,8 @@ async fn macro_zero_copy_cow() { assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":"Zero copy params: false, true","id":0}"#); // serde_json will have to allocate a new string to replace `\t` with byte 0x09 (tab) - let (resp, _) = module - .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_zero_copy_cow","params":["\tfoo", "\tbar"],"id":0}"#) + let resp = module + .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_zero_copy_cow","params":["\tfoo", "\tbar"],"id":0}"#, tx) .await .unwrap(); assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":"Zero copy params: false, false","id":0}"#); diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index 32c20e1e12..5ab1289b29 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -26,7 +26,7 @@ mod helpers; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::time::Duration; use futures::StreamExt; @@ -37,6 +37,7 @@ use jsonrpsee::core::EmptyServerParams; use jsonrpsee::types::error::{CallError, ErrorCode, ErrorObject, PARSE_ERROR_CODE}; use jsonrpsee::types::{ErrorObjectOwned, Params}; use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc; use tokio::time::interval; use tokio_stream::wrappers::IntervalStream; @@ -252,7 +253,8 @@ async fn subscribing_without_server() { }) .unwrap(); - let mut my_sub = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap(); + let mut my_sub = module.subscribe_unbounded("my_sub", EmptyServerParams::new()).await.unwrap(); + for i in (0..=2).rev() { let (val, id) = my_sub.next::().await.unwrap().unwrap(); assert_eq!(val, std::char::from_digit(i, 10).unwrap()); @@ -287,11 +289,12 @@ async fn close_test_subscribing_without_server() { }) .unwrap(); - let mut my_sub = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap(); + let mut my_sub = module.subscribe_unbounded("my_sub", EmptyServerParams::new()).await.unwrap(); let (val, id) = my_sub.next::().await.unwrap().unwrap(); assert_eq!(&val, "lo"); assert_eq!(&id, my_sub.subscription_id()); - let mut my_sub2 = std::mem::ManuallyDrop::new(module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap()); + let mut my_sub2 = + std::mem::ManuallyDrop::new(module.subscribe_unbounded("my_sub", EmptyServerParams::new()).await.unwrap()); // Close the subscription to ensure it doesn't return any items. my_sub.close(); @@ -333,7 +336,7 @@ async fn subscribing_without_server_bad_params() { }) .unwrap(); - let sub = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap_err(); + let sub = module.subscribe_unbounded("my_sub", EmptyServerParams::new()).await.unwrap_err(); assert!( matches!(sub, Error::Call(CallError::Custom(e)) if e.message().contains("invalid length 0, expected an array of length 1 at line 1 column 2") && e.code() == ErrorCode::InvalidParams.code()) @@ -355,21 +358,21 @@ async fn subscribe_unsubscribe_without_server() { .unwrap(); async fn subscribe_and_assert(module: &RpcModule<()>) { - let sub = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap(); - + let sub = module.subscribe_unbounded("my_sub", EmptyServerParams::new()).await.unwrap(); let ser_id = serde_json::to_string(sub.subscription_id()).unwrap(); + let (tx, _) = mpsc::channel(1); assert!(!sub.is_closed()); // Unsubscribe should be valid. let unsub_req = format!("{{\"jsonrpc\":\"2.0\",\"method\":\"my_unsub\",\"params\":[{}],\"id\":1}}", ser_id); - let (resp, _) = module.raw_json_request(&unsub_req).await.unwrap(); + let resp = module.raw_json_request(&unsub_req, tx.clone()).await.unwrap(); assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":true,"id":1}"#); // Unsubscribe already performed; should be error. let unsub_req = format!("{{\"jsonrpc\":\"2.0\",\"method\":\"my_unsub\",\"params\":[{}],\"id\":1}}", ser_id); - let (resp, _) = module.raw_json_request(&unsub_req).await.unwrap(); + let resp = module.raw_json_request(&unsub_req, tx).await.unwrap(); assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":false,"id":1}"#); } @@ -392,7 +395,7 @@ async fn rejected_subscription_without_server() { }) .unwrap(); - let sub_err = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap_err(); + let sub_err = module.subscribe_unbounded("my_sub", EmptyServerParams::new()).await.unwrap_err(); assert!( matches!(sub_err, Error::Call(CallError::Custom(e)) if e.message().contains("rejected") && e.code() == PARSE_ERROR_CODE) ); @@ -411,8 +414,74 @@ async fn reject_works() { }) .unwrap(); - let sub_err = module.subscribe("my_sub", EmptyServerParams::new()).await.unwrap_err(); + let sub_err = module.subscribe_unbounded("my_sub", EmptyServerParams::new()).await.unwrap_err(); assert!( matches!(sub_err, Error::Call(CallError::Custom(e)) if e.message().contains("rejected") && e.code() == PARSE_ERROR_CODE) ); } + +#[tokio::test] +async fn bounded_subscription_work() { + init_logger(); + + let (tx, mut rx) = mpsc::unbounded_channel::(); + let mut module = RpcModule::new(tx); + + module + .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, mut ctx| async move { + println!("accept"); + let mut sink = pending.accept().await?; + + let mut stream = IntervalStream::new(interval(std::time::Duration::from_millis(100))) + .enumerate() + .map(|(n, _)| n) + .take(6); + let fail = std::sync::Arc::make_mut(&mut ctx); + let mut buf = VecDeque::new(); + + while let Some(n) = stream.next().await { + let msg = sink.build_message(&n).expect("usize infallible; qed"); + + match sink.try_send(msg) { + Err(TrySendError::Closed(_)) => panic!("This is a bug"), + Err(TrySendError::Full(m)) => { + buf.push_back(m); + } + Ok(_) => (), + } + } + + if !buf.is_empty() { + fail.send("Full".to_string()).unwrap(); + } + + while let Some(m) = buf.pop_front() { + match sink.try_send(m) { + Err(TrySendError::Closed(_)) => panic!("This is a bug"), + Err(TrySendError::Full(m)) => { + buf.push_front(m); + } + Ok(_) => (), + } + + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + } + + Ok(()) + }) + .unwrap(); + + // create a bounded subscription and don't poll it + // after 3 items has been produced messages will be dropped. + let mut sub = module.subscribe_bounded("my_sub", EmptyServerParams::new(), 3).await.unwrap(); + + // assert that some items couldn't be sent. + assert_eq!(rx.recv().await, Some("Full".to_string())); + + // the subscription should continue produce items are consumed + // and the failed messages should be able to go succeed. + for exp in 0..6 { + let (item, _) = sub.next::().await.unwrap().unwrap(); + assert_eq!(item, exp); + } +} From f63c3ffe163ec43f31bc6489c225121804977f34 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 2 Feb 2023 18:55:25 +0100 Subject: [PATCH 28/60] doc fixes --- core/src/server/rpc_module.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index aaa26169f7..a50f26aa7e 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -337,7 +337,7 @@ impl Methods { /// Make a request (JSON-RPC method call or subscription) by using raw JSON. /// /// This is a low-level API where you might be able build "in-memory" client - /// but it's not ergonomic to use, see [`RpcModule::call`] and [`RpcModule::subscribe_bounded`] + /// but it's not ergonomic to use, see [`Methods::call`] and [`Methods::subscribe_bounded`] /// for more ergonomic APIs to perform method calls or subscriptions for testing or other purposes. /// /// Returns the raw JSON response to the call, additional responses are sent out @@ -411,7 +411,7 @@ impl Methods { /// /// The params must be serializable as JSON array, see [`ToRpcParams`] for further documentation. /// - /// Returns [`Subscription`] on success which can used to get results from the subscriptions. + /// Returns [`Subscription`] on success which can used to get results from the subscription. /// /// # Examples /// @@ -440,7 +440,7 @@ impl Methods { self.subscribe_bounded(sub_method, params, u32::MAX as usize).await } - /// Similar to `RpcMethods::subscribe_unbounded` but it's a using bounded channel. + /// Similar to [`Methods::subscribe_unbounded`] but it's a using bounded channel. pub async fn subscribe_bounded( &self, sub_method: &str, @@ -630,7 +630,7 @@ impl RpcModule { /// * `unsubscription_method` - name of the method to call to terminate a subscription /// * `callback` - A callback to invoke on each subscription; it takes three parameters: /// - [`Params`]: JSON-RPC parameters in the subscription call. - /// - [`SubscriptionSink`]: A sink to send messages to the subscriber. + /// - [`PendingSubscriptionSink`]: A pending subscription waiting to accepted. /// - Context: Any type that can be embedded into the [`RpcModule`]. /// /// # Examples @@ -925,7 +925,7 @@ impl SubscriptionSink { /// channel is full or the connection/subscription is closed /// /// - /// This differs from [`SubscriptionSink::send`] as it will until there is capacity + /// This differs from [`SubscriptionSink::send`] where it will until there is capacity /// in the channel. pub fn try_send(&mut self, msg: SubscriptionMessage) -> Result<(), TrySendError> { // Only possible to trigger when the connection is dropped. From 8fa7172a4aaecea195ad5aec6a2be988fc052c66 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 2 Feb 2023 19:13:52 +0100 Subject: [PATCH 29/60] fix more nits --- core/src/server/rpc_module.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index a50f26aa7e..4586ebfbf5 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -849,7 +849,6 @@ impl PendingSubscriptionSink { let permit = self.inner.reserve().await.map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; Self::answer_subscription(permit, response, self.subscribe).await?; - tracing::info!("accepted"); if success { let (tx, rx) = mpsc::channel(1); From 6abfcba628021d8691cf8b762d9a6fd728aaac8c Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 3 Feb 2023 14:12:03 +0100 Subject: [PATCH 30/60] refactor: pipe_from_stream --- core/src/server/rpc_module.rs | 88 ++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 23 deletions(-) diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 4586ebfbf5..4da5cb4b0a 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -224,6 +224,37 @@ impl Debug for MethodKind { } } +/// A future that only resolves if a future was set. +pub enum PendingOrFuture<'a> { + /// This will never resolve. + Pending, + /// Resolves once the future is completed. + Future(BoxFuture<'a, Result<(), DisconnectError>>), +} + +impl<'a> std::fmt::Debug for PendingOrFuture<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let msg = match self { + Self::Pending => "pending", + Self::Future(_) => "future", + }; + f.write_str(msg) + } +} + +impl<'a> std::future::Future for PendingOrFuture<'a> { + type Output = Result<(), DisconnectError>; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { + let this = std::pin::Pin::into_inner(self); + + match this { + Self::Pending => std::task::Poll::Pending, + Self::Future(fut) => fut.poll_unpin(cx), + } + } +} + /// Reference-counted, clone-on-write collection of synchronous and asynchronous methods. #[derive(Default, Debug, Clone)] pub struct Methods { @@ -1007,9 +1038,9 @@ impl SubscriptionSink { /// Ok(()) /// }); /// ``` - pub async fn pipe_from_try_stream(&mut self, f: F, mut stream: S) -> SubscriptionClosed + pub async fn pipe_from_try_stream<'a, S, T, F, E>(&'a mut self, f: F, stream: S) -> SubscriptionClosed where - F: Fn(Option, SubscriptionMessage) -> SubscriptionMessage, + F: Fn(Option>, PendingOrFuture<'a>) -> PendingOrFuture<'a>, S: TryStream + Unpin, T: Serialize, E: std::fmt::Display, @@ -1019,40 +1050,51 @@ impl SubscriptionSink { SubscriptionClosed::Failed(err) } - let mut last = None; + let mut pending_send = PendingOrFuture::Pending; + let closed = self.closed(); + + tokio::pin!(closed, stream); + + let mut next_item = stream.try_next(); loop { - tokio::select! { - // add priority for the closed future - // if that fires there's no point reading the stream - biased; - _ = self.closed() => { - break SubscriptionClosed::RemotePeerAborted + match futures_util::future::select(closed, futures_util::future::select(pending_send, next_item)).await { + Either::Left((_, _)) => { + break SubscriptionClosed::RemotePeerAborted; + } + + Either::Right((Either::Left((maybe_sent, n)), c)) => { + if maybe_sent.is_err() { + break SubscriptionClosed::RemotePeerAborted; + } + pending_send = PendingOrFuture::Pending; + next_item = n; + closed = c; } - item = stream.try_next() => { + Either::Right((Either::Right((item, pending)), c)) => { let item = match item { Ok(Some(item)) => item, Ok(None) => break SubscriptionClosed::Success, Err(e) => break err(e), }; - let next = match self.build_message(&item) { + let msg = match self.build_message(&item) { Ok(msg) => msg, Err(e) => break err(e), }; - let msg = f(last.take(), next); + let next: BoxFuture<_> = Box::pin(self.send(msg)); - match self.try_send(msg) { - Ok(_) => (), - Err(TrySendError::Closed(_)) => break SubscriptionClosed::RemotePeerAborted, - Err(TrySendError::Full(m)) => { - tracing::debug!("Failed to send message the buffer is full"); - last = Some(m); - } - } - }, + let pending = match pending { + PendingOrFuture::Pending => None, + fut => Some(fut), + }; + + pending_send = f(pending, PendingOrFuture::Future(next)); + closed = c; + next_item = stream.try_next(); + } } } } @@ -1078,9 +1120,9 @@ impl SubscriptionSink { /// Ok(()) /// }); /// ``` - pub async fn pipe_from_stream(&mut self, f: F, stream: S) -> SubscriptionClosed + pub async fn pipe_from_stream<'a, S, T, F>(&'a mut self, f: F, stream: S) -> SubscriptionClosed where - F: Fn(Option, SubscriptionMessage) -> SubscriptionMessage, + F: Fn(Option>, PendingOrFuture<'a>) -> PendingOrFuture<'a>, S: Stream + Unpin, T: Serialize, { From 2227a7ba0756bec385443072321c390a36ebf503 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 3 Feb 2023 14:30:20 +0100 Subject: [PATCH 31/60] fix examples: revert unintentional change --- examples/examples/proc_macro.rs | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/examples/examples/proc_macro.rs b/examples/examples/proc_macro.rs index 5fb13bd18f..eebedc3cbb 100644 --- a/examples/examples/proc_macro.rs +++ b/examples/examples/proc_macro.rs @@ -26,22 +26,15 @@ use std::net::SocketAddr; -use jsonrpsee::core::{async_trait, client::Subscription, Error, SubscriptionResult}; +use jsonrpsee::core::{async_trait, client::Subscription, Error}; use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::{PendingSubscriptionSink, ServerBuilder}; +use jsonrpsee::server::{ServerBuilder, SubscriptionSink}; +use jsonrpsee::types::SubscriptionResult; use jsonrpsee::ws_client::WsClientBuilder; type ExampleHash = [u8; 32]; type ExampleStorageKey = Vec; -#[rpc(client, server)] -pub trait DupOverride { - #[subscription(name = "subscribeOne" => "override", item = u8)] - async fn one(&self) -> jsonrpsee::core::SubscriptionResult; - /*#[subscription(name = "subscribeTwo" => "override", item = u8)] - async fn two(&self) -> jsonrpsee::core::SubscriptionResult;*/ -} - #[rpc(server, client, namespace = "state")] pub trait Rpc where @@ -53,7 +46,7 @@ where /// Subscription that takes a `StorageKey` as input and produces a `Vec`. #[subscription(name = "subscribeStorage" => "override", item = Vec)] - async fn subscribe_storage(&self, keys: Option>) -> SubscriptionResult; + fn subscribe_storage(&self, keys: Option>); } pub struct RpcServerImpl; @@ -69,15 +62,12 @@ impl RpcServer for RpcServerImpl { } // Note that the server's subscription method must return `SubscriptionResult`. - async fn subscribe_storage( + fn subscribe_storage( &self, - pending: PendingSubscriptionSink, + mut sink: SubscriptionSink, _keys: Option>, ) -> SubscriptionResult { - let sink = pending.accept().await?; - let msg = sink.build_message(&vec![[0; 32]]).unwrap(); - sink.send(msg).await.unwrap(); - + let _ = sink.send(&vec![[0; 32]]); Ok(()) } } @@ -99,8 +89,6 @@ async fn main() -> anyhow::Result<()> { RpcClient::::subscribe_storage(&client, None).await.unwrap(); assert_eq!(Some(vec![[0; 32]]), sub.next().await.transpose().unwrap()); - sub.unsubscribe().await.unwrap(); - Ok(()) } From 17a98ee4d8d09eda5f570724c147c5709fd542e6 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 3 Feb 2023 15:28:39 +0100 Subject: [PATCH 32/60] address grumbles --- core/src/server/rpc_module.rs | 101 ++++++++++++++++++++++---------- examples/examples/proc_macro.rs | 15 ++--- server/src/server.rs | 38 +----------- server/src/transport/ws.rs | 28 ++++----- tests/tests/proc_macros.rs | 19 +++--- tests/tests/rpc_module.rs | 10 ++-- 6 files changed, 108 insertions(+), 103 deletions(-) diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 4da5cb4b0a..d34813933d 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -72,12 +72,6 @@ pub type ConnectionId = usize; /// Max response size. pub type MaxResponseSize = usize; -/// Raw response from an RPC -/// A 3-tuple containing: -/// - Call result as a `String`, -/// - a [`mpsc::Receiver`] to receive future subscription results -pub type RawRpcResponse = (MethodResponse, mpsc::Receiver, MethodSink); - /// TrySendError #[derive(Debug)] pub enum TrySendError { @@ -87,7 +81,7 @@ pub enum TrySendError { Full(SubscriptionMessage), } -#[derive(Debug)] +#[derive(Debug, Clone)] /// Represents whether a subscription was answered or not. pub enum SubscriptionAnswered { /// The subscription was already answered and doesn't need to answered again. @@ -143,6 +137,44 @@ impl<'a> std::fmt::Debug for ConnState<'a> { type Subscribers = Arc)>>>; +/// This represent a response to a RPC call +/// and `Subscribe` calls are handled differently +/// because we want to prevent subscriptions to start +/// before the actual subscription call has been answered. +#[derive(Debug, Clone)] +pub enum CallResponse { + /// The subscription callback itself sends back the result + /// so it must not be sent back again. + Subscribe(SubscriptionAnswered), + + /// Treat it as ordinary call. + Call(MethodResponse), +} + +impl CallResponse { + /// Extract the JSON-RPC response. + pub fn as_response(&self) -> &MethodResponse { + match &self { + Self::Subscribe(r) => match r { + SubscriptionAnswered::Yes(r) => r, + SubscriptionAnswered::No(r) => r, + }, + Self::Call(r) => r, + } + } + + /// Convert the `CallResponse` to JSON-RPC response. + pub fn into_response(self) -> MethodResponse { + match self { + Self::Subscribe(r) => match r { + SubscriptionAnswered::Yes(r) => r, + SubscriptionAnswered::No(r) => r, + }, + Self::Call(r) => r, + } + } +} + /// Subscription message. #[derive(Debug, Clone)] pub struct SubscriptionMessage(pub(crate) String); @@ -353,7 +385,8 @@ impl Methods { let req = Request::new(method.into(), params.as_ref().map(|p| p.as_ref()), Id::Number(0)); tracing::trace!("[Methods::call] Method: {:?}, params: {:?}", method, params); let (tx, _rx) = mpsc::channel(1); - let resp = self.inner_call(req, tx).await; + let call_resp = self.inner_call(req, tx).await; + let resp = call_resp.as_response(); if resp.success { serde_json::from_str::>(&resp.result).map(|r| r.result).map_err(Into::into) @@ -371,8 +404,8 @@ impl Methods { /// but it's not ergonomic to use, see [`Methods::call`] and [`Methods::subscribe_bounded`] /// for more ergonomic APIs to perform method calls or subscriptions for testing or other purposes. /// - /// Returns the raw JSON response to the call, additional responses are sent out - /// on the the receiver connected to the sender as passed to this method. + /// After this method returns the responses can be read on the corresponding `Receiver` to the `Sender` + /// that was passed into to the call. /// /// # Examples /// @@ -394,43 +427,46 @@ impl Methods { /// }).unwrap(); /// /// let (tx, mut rx) = tokio::sync::mpsc::channel(16); - /// let resp = module.raw_json_request(r#"{"jsonrpc":"2.0","method":"hi","id":0}"#, tx).await.unwrap(); - /// let resp = serde_json::from_str::>(&resp.result).unwrap(); - /// // ignore duplicated subscription call response. - /// _ = rx.recv().await.unwrap(); + /// module.raw_json_request(r#"{"jsonrpc":"2.0","method":"hi","id":0}"#, tx).await.unwrap(); /// let sub_resp = rx.recv().await.unwrap(); + /// // the subscription ID is sent in the `result` field. + /// let sub_resp = serde_json::from_str::>(&sub_resp).unwrap(); + /// + /// let sub_notif = rx.recv().await.unwrap(); /// assert_eq!( - /// format!(r#"{{"jsonrpc":"2.0","method":"hi","params":{{"subscription":{},"result":"one answer"}}}}"#, resp.result), - /// sub_resp + /// format!(r#"{{"jsonrpc":"2.0","method":"hi","params":{{"subscription":{},"result":"one answer"}}}}"#, sub_resp.result), + /// sub_notif /// ); /// } /// ``` - pub async fn raw_json_request(&self, request: &str, tx: mpsc::Sender) -> Result { + pub async fn raw_json_request(&self, request: &str, tx: mpsc::Sender) -> Result<(), Error> { tracing::trace!("[Methods::raw_json_request] Request: {:?}", request); let req: Request = serde_json::from_str(request)?; - let resp = self.inner_call(req, tx).await; - Ok(resp) + if let CallResponse::Call(r) = self.inner_call(req, tx.clone()).await { + tx.send(r.result).await.map_err(|_| Error::Custom("Connection dropped; you have to keep the corresponding `Receiver` for `Methods::raw_json_request` to work".to_string()))?; + } + + Ok(()) } /// Execute a callback. - async fn inner_call(&self, req: Request<'_>, tx: mpsc::Sender) -> MethodResponse { + async fn inner_call(&self, req: Request<'_>, tx: mpsc::Sender) -> CallResponse { let id = req.id.clone(); let params = Params::new(req.params.map(|params| params.get())); let response = match self.method(&req.method).map(|c| &c.callback) { - None => MethodResponse::error(req.id, ErrorObject::from(ErrorCode::MethodNotFound)), - Some(MethodKind::Sync(cb)) => (cb)(id, params, usize::MAX), - Some(MethodKind::Async(cb)) => (cb)(id.into_owned(), params.into_owned(), 0, usize::MAX).await, + None => CallResponse::Call(MethodResponse::error(req.id, ErrorObject::from(ErrorCode::MethodNotFound))), + Some(MethodKind::Sync(cb)) => CallResponse::Call((cb)(id, params, usize::MAX)), + Some(MethodKind::Async(cb)) => { + CallResponse::Call((cb)(id.into_owned(), params.into_owned(), 0, usize::MAX).await) + } Some(MethodKind::Subscription(cb)) => { let conn_state = ConnState { conn_id: 0, id_provider: &RandomIntegerIdProvider }; let res = (cb)(id, params, MethodSink::new(tx), conn_state).await; - match res { - SubscriptionAnswered::Yes(r) => r, - SubscriptionAnswered::No(r) => r, - } + CallResponse::Subscribe(res) } - Some(MethodKind::Unsubscription(cb)) => (cb)(id, params, 0, usize::MAX), + Some(MethodKind::Unsubscription(cb)) => CallResponse::Call((cb)(id, params, 0, usize::MAX)), }; tracing::trace!("[Methods::inner_call] Method: {}, response: {:?}", req.method, response); @@ -471,7 +507,7 @@ impl Methods { self.subscribe_bounded(sub_method, params, u32::MAX as usize).await } - /// Similar to [`Methods::subscribe_unbounded`] but it's a using bounded channel. + /// Similar to [`Methods::subscribe_unbounded`] but it's using a bounded channel. pub async fn subscribe_bounded( &self, sub_method: &str, @@ -484,7 +520,8 @@ impl Methods { tracing::trace!("[Methods::subscribe] Method: {}, params: {:?}", sub_method, params); - let response = self.inner_call(req, tx.clone()).await; + let call_resp = self.inner_call(req, tx.clone()).await; + let response = call_resp.as_response(); let subscription_response = match serde_json::from_str::>(&response.result) { Ok(r) => r, @@ -996,8 +1033,8 @@ impl SubscriptionSink { /// when items gets produced by the stream. /// /// The underlying sink is a bounded channel which may not have room for new items - /// if the client can't keep up with all the messages. Thus, you need to provide a closure - /// with a policy what to then the underlying channel has not space to "buffer" more messages. + /// if the client can't keep up with all the messages from the stream. Thus, you need to provide a closure + /// with a policy what to do when the underlying channel has not space to "buffer" more messages. /// /// The underlying stream must produce `Result values, see [`futures_util::TryStream`] for further information. /// diff --git a/examples/examples/proc_macro.rs b/examples/examples/proc_macro.rs index eebedc3cbb..a7b819b546 100644 --- a/examples/examples/proc_macro.rs +++ b/examples/examples/proc_macro.rs @@ -26,10 +26,9 @@ use std::net::SocketAddr; -use jsonrpsee::core::{async_trait, client::Subscription, Error}; +use jsonrpsee::core::{async_trait, client::Subscription, Error, SubscriptionResult}; use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::{ServerBuilder, SubscriptionSink}; -use jsonrpsee::types::SubscriptionResult; +use jsonrpsee::server::{PendingSubscriptionSink, ServerBuilder}; use jsonrpsee::ws_client::WsClientBuilder; type ExampleHash = [u8; 32]; @@ -46,7 +45,7 @@ where /// Subscription that takes a `StorageKey` as input and produces a `Vec`. #[subscription(name = "subscribeStorage" => "override", item = Vec)] - fn subscribe_storage(&self, keys: Option>); + async fn subscribe_storage(&self, keys: Option>) -> SubscriptionResult; } pub struct RpcServerImpl; @@ -62,12 +61,14 @@ impl RpcServer for RpcServerImpl { } // Note that the server's subscription method must return `SubscriptionResult`. - fn subscribe_storage( + async fn subscribe_storage( &self, - mut sink: SubscriptionSink, + pending: PendingSubscriptionSink, _keys: Option>, ) -> SubscriptionResult { - let _ = sink.send(&vec![[0; 32]]); + let sink = pending.accept().await?; + let msg = sink.build_message(&vec![[0; 32]])?; + sink.send(msg).await?; Ok(()) } } diff --git a/server/src/server.rs b/server/src/server.rs index 4ece319838..3eb012d676 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -42,9 +42,8 @@ use futures_util::io::{BufReader, BufWriter}; use hyper::body::HttpBody; use jsonrpsee_core::id_providers::RandomIntegerIdProvider; -use jsonrpsee_core::server::helpers::MethodResponse; use jsonrpsee_core::server::host_filtering::AllowHosts; -use jsonrpsee_core::server::rpc_module::{Methods, SubscriptionAnswered}; +use jsonrpsee_core::server::rpc_module::Methods; use jsonrpsee_core::traits::IdProvider; use jsonrpsee_core::{http_helpers, Error, TEN_MB_SIZE_BYTES}; @@ -517,41 +516,6 @@ impl Builder { } } -/// This represent a response to a RPC call -/// and `Subscribe` calls are handled differently -/// because we want to prevent subscriptions to start -/// before the actual subscription call has been answered. -pub(crate) enum MethodResult { - /// The subscription callback itself sends back the result - /// so it must not be sent back again. - Subscribe(SubscriptionAnswered), - - /// Treat it as ordinary call. - Call(MethodResponse), -} - -impl MethodResult { - pub(crate) fn as_response(&self) -> &MethodResponse { - match &self { - Self::Subscribe(r) => match r { - SubscriptionAnswered::Yes(r) => r, - SubscriptionAnswered::No(r) => r, - }, - Self::Call(r) => r, - } - } - - pub(crate) fn into_response(self) -> MethodResponse { - match self { - Self::Subscribe(r) => match r { - SubscriptionAnswered::Yes(r) => r, - SubscriptionAnswered::No(r) => r, - }, - Self::Call(r) => r, - } - } -} - /// Data required by the server to handle requests. #[derive(Debug, Clone)] pub(crate) struct ServiceData { diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index d9f458e8bd..18a439c746 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -4,7 +4,7 @@ use std::time::Duration; use crate::future::{FutureDriver, StopHandle}; use crate::logger::{self, Logger, TransportProtocol}; -use crate::server::{MethodResult, ServiceData}; +use crate::server::ServiceData; use futures_util::future::{self, Either}; use futures_util::io::{BufReader, BufWriter}; @@ -12,7 +12,7 @@ use futures_util::stream::FuturesOrdered; use futures_util::{Future, FutureExt, StreamExt}; use hyper::upgrade::Upgraded; use jsonrpsee_core::server::helpers::{prepare_error, BatchResponse, BatchResponseBuilder, MethodResponse, MethodSink}; -use jsonrpsee_core::server::rpc_module::{ConnState, MethodKind, Methods, SubscriptionAnswered}; +use jsonrpsee_core::server::rpc_module::{CallResponse, ConnState, MethodKind, Methods, SubscriptionAnswered}; use jsonrpsee_core::tracing::{rx_log_from_json, tx_log_from_str}; use jsonrpsee_core::traits::IdProvider; use jsonrpsee_core::{Error, JsonRawValue}; @@ -147,17 +147,17 @@ pub(crate) async fn process_batch_request(b: Batch<'_, L>) -> Option< } } -pub(crate) async fn process_single_request(data: Vec, call: CallData<'_, L>) -> MethodResult { +pub(crate) async fn process_single_request(data: Vec, call: CallData<'_, L>) -> CallResponse { if let Ok(req) = serde_json::from_slice::(&data) { execute_call_with_tracing(req, call).await } else { let (id, code) = prepare_error(&data); - MethodResult::Call(MethodResponse::error(id, ErrorObject::from(code))) + CallResponse::Call(MethodResponse::error(id, ErrorObject::from(code))) } } #[instrument(name = "method_call", fields(method = req.method.as_ref()), skip(call, req), level = "TRACE")] -pub(crate) async fn execute_call_with_tracing<'a, L: Logger>(req: Request<'a>, call: CallData<'_, L>) -> MethodResult { +pub(crate) async fn execute_call_with_tracing<'a, L: Logger>(req: Request<'a>, call: CallData<'_, L>) -> CallResponse { execute_call(req, call).await } @@ -166,7 +166,7 @@ pub(crate) async fn execute_call_with_tracing<'a, L: Logger>(req: Request<'a>, c /// /// Returns `(MethodResponse, None)` on every call that isn't a subscription /// Otherwise `(MethodResponse, Some(PendingSubscriptionCallTx)`. -pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData<'_, L>) -> MethodResult { +pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData<'_, L>) -> CallResponse { let CallData { methods, max_response_body_size, max_log_length, conn_id, id_provider, sink, logger, request_start } = call; @@ -180,12 +180,12 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData None => { logger.on_call(name, params.clone(), logger::MethodKind::Unknown, TransportProtocol::WebSocket); let response = MethodResponse::error(id, ErrorObject::from(ErrorCode::MethodNotFound)); - MethodResult::Call(response) + CallResponse::Call(response) } Some((name, method)) => match &method.inner() { MethodKind::Sync(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::MethodCall, TransportProtocol::WebSocket); - MethodResult::Call((callback)(id, params, max_response_body_size as usize)) + CallResponse::Call((callback)(id, params, max_response_body_size as usize)) } MethodKind::Async(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::MethodCall, TransportProtocol::WebSocket); @@ -194,7 +194,7 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData let params = params.into_owned(); let response = (callback)(id, params, conn_id, max_response_body_size as usize).await; - MethodResult::Call(response) + CallResponse::Call(response) } MethodKind::Subscription(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::Subscription, TransportProtocol::WebSocket); @@ -202,14 +202,14 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData let conn_state = ConnState { conn_id, id_provider }; let response = callback(id.clone(), params, sink.clone(), conn_state).await; - MethodResult::Subscribe(response) + CallResponse::Subscribe(response) } MethodKind::Unsubscription(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::Unsubscription, TransportProtocol::WebSocket); // Don't adhere to any resource or subscription limits; always let unsubscribing happen! let result = callback(id, params, conn_id, max_response_body_size as usize); - MethodResult::Call(result) + CallResponse::Call(result) } }, }; @@ -343,14 +343,14 @@ pub(crate) async fn background_task( }; match process_single_request(data, call).await { - MethodResult::Subscribe(SubscriptionAnswered::Yes(r)) => { + CallResponse::Subscribe(SubscriptionAnswered::Yes(r)) => { logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); } - MethodResult::Subscribe(SubscriptionAnswered::No(r)) => { + CallResponse::Subscribe(SubscriptionAnswered::No(r)) => { logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); sink_permit.send_raw(r.result); } - MethodResult::Call(r) => { + CallResponse::Call(r) => { logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); sink_permit.send_raw(r.result); } diff --git a/tests/tests/proc_macros.rs b/tests/tests/proc_macros.rs index 3a8dfdb931..78262cff0d 100644 --- a/tests/tests/proc_macros.rs +++ b/tests/tests/proc_macros.rs @@ -260,7 +260,7 @@ async fn macro_param_parsing() { async fn macro_optional_param_parsing() { let module = RpcServerImpl.into_rpc(); - let (tx, _) = mpsc::channel(1); + let (tx, mut rx) = mpsc::channel(1); // Optional param omitted at tail let res: String = module.call("foo_optional_params", [42_u64, 70]).await.unwrap(); @@ -272,11 +272,12 @@ async fn macro_optional_param_parsing() { assert_eq!(&res, "Called with: 42, None, Some(70)"); // Named params using a map - let resp = module + module .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_optional_params","params":{"a":22,"c":50},"id":0}"#, tx) .await .unwrap(); - assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":"Called with: 22, None, Some(50)","id":0}"#); + let resp = rx.recv().await.unwrap(); + assert_eq!(resp, r#"{"jsonrpc":"2.0","result":"Called with: 22, None, Some(50)","id":0}"#); } #[tokio::test] @@ -290,11 +291,13 @@ async fn macro_lifetimes_parsing() { #[tokio::test] async fn macro_zero_copy_cow() { + init_logger(); + let module = RpcServerImpl.into_rpc(); - let (tx, _) = mpsc::channel(1); + let (tx, mut rx) = mpsc::channel(16); - let resp = module + module .raw_json_request( r#"{"jsonrpc":"2.0","method":"foo_zero_copy_cow","params":["foo", "bar"],"id":0}"#, tx.clone(), @@ -303,14 +306,14 @@ async fn macro_zero_copy_cow() { .unwrap(); // std::borrow::Cow always deserialized to owned variant here - assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":"Zero copy params: false, true","id":0}"#); + assert_eq!(rx.recv().await.unwrap(), r#"{"jsonrpc":"2.0","result":"Zero copy params: false, true","id":0}"#); // serde_json will have to allocate a new string to replace `\t` with byte 0x09 (tab) - let resp = module + module .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_zero_copy_cow","params":["\tfoo", "\tbar"],"id":0}"#, tx) .await .unwrap(); - assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":"Zero copy params: false, false","id":0}"#); + assert_eq!(rx.recv().await.unwrap(), r#"{"jsonrpc":"2.0","result":"Zero copy params: false, false","id":0}"#); } // Disabled on MacOS as GH CI timings on Mac vary wildly (~100ms) making this test fail. diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index 5ab1289b29..07cd8d3264 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -360,21 +360,21 @@ async fn subscribe_unsubscribe_without_server() { async fn subscribe_and_assert(module: &RpcModule<()>) { let sub = module.subscribe_unbounded("my_sub", EmptyServerParams::new()).await.unwrap(); let ser_id = serde_json::to_string(sub.subscription_id()).unwrap(); - let (tx, _) = mpsc::channel(1); + let (tx, mut rx) = mpsc::channel(1); assert!(!sub.is_closed()); // Unsubscribe should be valid. let unsub_req = format!("{{\"jsonrpc\":\"2.0\",\"method\":\"my_unsub\",\"params\":[{}],\"id\":1}}", ser_id); - let resp = module.raw_json_request(&unsub_req, tx.clone()).await.unwrap(); + module.raw_json_request(&unsub_req, tx.clone()).await.unwrap(); - assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":true,"id":1}"#); + assert_eq!(rx.recv().await.unwrap(), r#"{"jsonrpc":"2.0","result":true,"id":1}"#); // Unsubscribe already performed; should be error. let unsub_req = format!("{{\"jsonrpc\":\"2.0\",\"method\":\"my_unsub\",\"params\":[{}],\"id\":1}}", ser_id); - let resp = module.raw_json_request(&unsub_req, tx).await.unwrap(); + module.raw_json_request(&unsub_req, tx).await.unwrap(); - assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":false,"id":1}"#); + assert_eq!(rx.recv().await.unwrap(), r#"{"jsonrpc":"2.0","result":false,"id":1}"#); } let sub1 = subscribe_and_assert(&module); From 9a4cc19f37a4452741ec044134b724ab90a64f55 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 3 Feb 2023 16:41:01 +0100 Subject: [PATCH 33/60] revert: don't require subscriptions to return Result --- examples/examples/proc_macro.rs | 2 +- proc-macros/src/helpers.rs | 2 +- proc-macros/src/lib.rs | 6 ++--- proc-macros/src/rpc_macro.rs | 23 +++---------------- .../ui/correct/alias_doesnt_use_namespace.rs | 2 +- proc-macros/tests/ui/correct/basic.rs | 6 ++--- proc-macros/tests/ui/correct/only_client.rs | 2 +- proc-macros/tests/ui/correct/only_server.rs | 2 +- .../tests/ui/correct/parse_angle_brackets.rs | 2 +- .../tests/ui/correct/rpc_deny_missing_docs.rs | 2 +- .../ui/incorrect/sub/sub_conflicting_alias.rs | 2 +- .../sub/sub_conflicting_alias.stderr | 4 ++-- .../ui/incorrect/sub/sub_dup_name_override.rs | 4 ++-- .../sub/sub_dup_name_override.stderr | 2 +- .../tests/ui/incorrect/sub/sub_empty_attr.rs | 2 +- .../ui/incorrect/sub/sub_name_override.rs | 2 +- .../ui/incorrect/sub/sub_name_override.stderr | 2 +- .../tests/ui/incorrect/sub/sub_no_item.rs | 2 +- .../tests/ui/incorrect/sub/sub_no_name.rs | 2 +- .../ui/incorrect/sub/sub_unsupported_field.rs | 2 +- tests/tests/proc_macros.rs | 8 +++---- 21 files changed, 32 insertions(+), 49 deletions(-) diff --git a/examples/examples/proc_macro.rs b/examples/examples/proc_macro.rs index a7b819b546..2b261b6c5a 100644 --- a/examples/examples/proc_macro.rs +++ b/examples/examples/proc_macro.rs @@ -45,7 +45,7 @@ where /// Subscription that takes a `StorageKey` as input and produces a `Vec`. #[subscription(name = "subscribeStorage" => "override", item = Vec)] - async fn subscribe_storage(&self, keys: Option>) -> SubscriptionResult; + async fn subscribe_storage(&self, keys: Option>); } pub struct RpcServerImpl; diff --git a/proc-macros/src/helpers.rs b/proc-macros/src/helpers.rs index e8790f66da..b1b3764916 100644 --- a/proc-macros/src/helpers.rs +++ b/proc-macros/src/helpers.rs @@ -80,7 +80,7 @@ fn find_jsonrpsee_crate(http_name: &str, ws_name: &str) -> Result RpcResult; /// /// #[subscription(name = "subscribe", item = Vec)] -/// async fn sub(&self) -> SubscriptionResult; +/// async fn sub(&self); /// } /// ``` /// diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index 744b3fe82c..26984b6efa 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -86,7 +86,7 @@ pub(crate) mod visitor; /// fn sync_method(&self) -> String; /// /// #[subscription(name = "subscribe", item = "String")] -/// async fn sub(&self) -> SubscriptionResult; +/// async fn sub(&self); /// } /// ``` /// @@ -248,7 +248,7 @@ pub(crate) mod visitor; /// /// } /// /// ``` /// #[subscription(name = "sub" => "subNotif", unsubscribe = "unsub", item = String)] -/// async fn sub_override_notif_method(&self) -> SubscriptionResult; +/// async fn sub_override_notif_method(&self); /// /// /// Use the same method name for both the `subscribe call` and `notifications` /// /// @@ -265,7 +265,7 @@ pub(crate) mod visitor; /// /// } /// /// ``` /// #[subscription(name = "subscribe", item = String)] -/// async fn sub(&self) -> SubscriptionResult; +/// async fn sub(&self); /// } /// /// // Structure that will implement the `MyRpcServer` trait. diff --git a/proc-macros/src/rpc_macro.rs b/proc-macros/src/rpc_macro.rs index b7ce246daa..26e547c439 100644 --- a/proc-macros/src/rpc_macro.rs +++ b/proc-macros/src/rpc_macro.rs @@ -295,26 +295,9 @@ impl RpcDescription { )); } - match method.sig.output.clone() { - syn::ReturnType::Type(_, ty) => { - if let syn::Type::Path(syn::TypePath { path, .. }) = *ty { - if let Some(ident) = path.get_ident() { - if ident != "SubscriptionResult" && ident != "Result" { - return Err(syn::Error::new_spanned( - method, - "Subscription methods must return `SubscriptionResult` or `Result`", - )); - } - } - } - } - _ => { - return Err(syn::Error::new_spanned( - method, - "Subscription methods must return `SubscriptionResult` or `Result`", - )); - } - }; + if !matches!(method.sig.output, syn::ReturnType::Default) { + return Err(syn::Error::new_spanned(method, "Subscription methods must not return anything")); + } if method.sig.asyncness.is_none() { return Err(syn::Error::new_spanned(method, "Subscription methods must be `async`")); diff --git a/proc-macros/tests/ui/correct/alias_doesnt_use_namespace.rs b/proc-macros/tests/ui/correct/alias_doesnt_use_namespace.rs index fdaba5b742..102487f850 100644 --- a/proc-macros/tests/ui/correct/alias_doesnt_use_namespace.rs +++ b/proc-macros/tests/ui/correct/alias_doesnt_use_namespace.rs @@ -8,7 +8,7 @@ pub trait Rpc { async fn async_method(&self, param_a: u8, param_b: String) -> RpcResult; #[subscription(name = "subscribeGetFood", item = String, aliases = ["getFood"], unsubscribe_aliases = ["unsubscribegetFood"])] - async fn sub(&self) -> SubscriptionResult; + async fn sub(&self); } fn main() {} diff --git a/proc-macros/tests/ui/correct/basic.rs b/proc-macros/tests/ui/correct/basic.rs index 6b0dcaba39..f58eb259aa 100644 --- a/proc-macros/tests/ui/correct/basic.rs +++ b/proc-macros/tests/ui/correct/basic.rs @@ -28,15 +28,15 @@ pub trait Rpc { fn sync_method(&self) -> RpcResult; #[subscription(name = "subscribe", item = String)] - async fn sub(&self) -> SubscriptionResult; + async fn sub(&self); #[subscription(name = "echo", unsubscribe = "unsubscribeEcho", aliases = ["ECHO"], item = u32, unsubscribe_aliases = ["NotInterested", "listenNoMore"])] - async fn sub_with_params(&self, val: u32) -> SubscriptionResult; + async fn sub_with_params(&self, val: u32); // This will send data to subscribers with the `method` field in the JSON payload set to `foo_subscribe_override` // because it's in the `foo` namespace. #[subscription(name = "subscribe_method" => "subscribe_override", item = u32)] - async fn sub_with_override_notif_method(&self) -> SubscriptionResult; + async fn sub_with_override_notif_method(&self); } pub struct RpcServerImpl; diff --git a/proc-macros/tests/ui/correct/only_client.rs b/proc-macros/tests/ui/correct/only_client.rs index 7d8e9bd852..bc9a4872f4 100644 --- a/proc-macros/tests/ui/correct/only_client.rs +++ b/proc-macros/tests/ui/correct/only_client.rs @@ -11,7 +11,7 @@ pub trait Rpc { fn sync_method(&self) -> RpcResult; #[subscription(name = "subscribe", item = String)] - async fn sub(&self) -> SubscriptionResult; + async fn sub(&self); } fn main() {} diff --git a/proc-macros/tests/ui/correct/only_server.rs b/proc-macros/tests/ui/correct/only_server.rs index 73281e90c5..4422b293d6 100644 --- a/proc-macros/tests/ui/correct/only_server.rs +++ b/proc-macros/tests/ui/correct/only_server.rs @@ -13,7 +13,7 @@ pub trait Rpc { fn sync_method(&self) -> RpcResult; #[subscription(name = "subscribe", item = String)] - async fn sub(&self) -> SubscriptionResult; + async fn sub(&self); } pub struct RpcServerImpl; diff --git a/proc-macros/tests/ui/correct/parse_angle_brackets.rs b/proc-macros/tests/ui/correct/parse_angle_brackets.rs index 50e9794850..c597b7afc6 100644 --- a/proc-macros/tests/ui/correct/parse_angle_brackets.rs +++ b/proc-macros/tests/ui/correct/parse_angle_brackets.rs @@ -12,6 +12,6 @@ fn main() { // angle braces need to be accounted for manually. item = TransactionStatus, )] - async fn dummy_subscription(&self) -> jsonrpsee::core::SubscriptionResult; + async fn dummy_subscription(&self); } } diff --git a/proc-macros/tests/ui/correct/rpc_deny_missing_docs.rs b/proc-macros/tests/ui/correct/rpc_deny_missing_docs.rs index aa61896d1a..b8b35aba98 100644 --- a/proc-macros/tests/ui/correct/rpc_deny_missing_docs.rs +++ b/proc-macros/tests/ui/correct/rpc_deny_missing_docs.rs @@ -13,7 +13,7 @@ pub trait ApiWithDocumentation { /// Subscription docs. #[subscription(name = "sub", unsubscribe = "unsub", item = String)] - async fn sub(&self) -> jsonrpsee::core::SubscriptionResult; + async fn sub(&self); } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.rs b/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.rs index ba1f5c38f7..600ca43df3 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.rs @@ -3,7 +3,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait DuplicatedSubAlias { #[subscription(name = "subscribeAlias", item = String, aliases = ["hello_is_goodbye"], unsubscribe_aliases = ["hello_is_goodbye"])] - async fn async_method(&self) -> jsonrpsee::core::SubscriptionResult; + async fn async_method(&self); } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.stderr b/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.stderr index d1b1582af5..e30ce56f7c 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.stderr +++ b/proc-macros/tests/ui/incorrect/sub/sub_conflicting_alias.stderr @@ -1,5 +1,5 @@ error: "hello_is_goodbye" is already defined - --> tests/ui/incorrect/sub/sub_conflicting_alias.rs:6:11 + --> $DIR/sub_conflicting_alias.rs:6:11 | -6 | async fn async_method(&self) -> jsonrpsee::core::SubscriptionResult; +6 | async fn async_method(&self); | ^^^^^^^^^^^^ diff --git a/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.rs b/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.rs index dba56b1a02..e81a9fed7d 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.rs @@ -4,9 +4,9 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait DupOverride { #[subscription(name = "subscribeOne" => "override", item = u8)] - async fn one(&self) -> jsonrpsee::core::SubscriptionResult; + async fn one(&self); #[subscription(name = "subscribeTwo" => "override", item = u8)] - async fn two(&self) -> jsonrpsee::core::SubscriptionResult; + async fn two(&self); } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.stderr b/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.stderr index a603a6f982..50efc5f4d5 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.stderr +++ b/proc-macros/tests/ui/incorrect/sub/sub_dup_name_override.stderr @@ -1,5 +1,5 @@ error: "override" is already defined --> $DIR/sub_dup_name_override.rs:9:11 | -9 | async fn two(&self) -> jsonrpsee::core::SubscriptionResult; +9 | async fn two(&self); | ^^^ diff --git a/proc-macros/tests/ui/incorrect/sub/sub_empty_attr.rs b/proc-macros/tests/ui/incorrect/sub/sub_empty_attr.rs index f50218dbec..e11dbe25b8 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_empty_attr.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_empty_attr.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait SubEmptyAttr { #[subscription()] - async fn sub(&self) -> jsonrpsee::core::SubscriptionResult; + async fn sub(&self); } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_name_override.rs b/proc-macros/tests/ui/incorrect/sub/sub_name_override.rs index 9ae56b0525..50c786ba35 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_name_override.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_name_override.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait DupName { #[subscription(name = "one" => "one", unsubscribe = "unsubscribeOne", item = u8)] - async fn one(&self) -> jsonrpsee::core::SubscriptionResult; + async fn one(&self); } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_name_override.stderr b/proc-macros/tests/ui/incorrect/sub/sub_name_override.stderr index e7a856215c..e4af34bcc3 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_name_override.stderr +++ b/proc-macros/tests/ui/incorrect/sub/sub_name_override.stderr @@ -1,5 +1,5 @@ error: "one" is already defined --> $DIR/sub_name_override.rs:7:11 | -7 | async fn one(&self) -> jsonrpsee::core::SubscriptionResult; +7 | async fn one(&self); | ^^^ diff --git a/proc-macros/tests/ui/incorrect/sub/sub_no_item.rs b/proc-macros/tests/ui/incorrect/sub/sub_no_item.rs index 0295efa4d3..d2c750a194 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_no_item.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_no_item.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait NoSubItem { #[subscription(name = "sub")] - async fn sub(&self) -> jsonrpsee::core::SubscriptionResult; + async fn sub(&self); } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_no_name.rs b/proc-macros/tests/ui/incorrect/sub/sub_no_name.rs index 900a4488c8..9a03ad21a5 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_no_name.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_no_name.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait NoSubName { #[subscription(item = String)] - async fn async_method(&self) -> SubscriptionResult; + async fn async_method(&self); } fn main() {} diff --git a/proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.rs b/proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.rs index 5835512eac..fbb22e367d 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.rs +++ b/proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.rs @@ -4,7 +4,7 @@ use jsonrpsee::proc_macros::rpc; #[rpc(client, server)] pub trait UnsupportedField { #[subscription(name = "sub", unsubscribe = "unsub", item = u8, magic = true)] - async fn sub(&self) -> SubscriptionResult; + async fn sub(&self); } fn main() {} diff --git a/tests/tests/proc_macros.rs b/tests/tests/proc_macros.rs index 78262cff0d..3453286cfa 100644 --- a/tests/tests/proc_macros.rs +++ b/tests/tests/proc_macros.rs @@ -55,10 +55,10 @@ mod rpc_impl { fn sync_method(&self) -> RpcResult; #[subscription(name = "sub", unsubscribe = "unsub", item = String)] - async fn sub(&self) -> SubscriptionResult; + async fn sub(&self); #[subscription(name = "echo", unsubscribe = "unsubscribe_echo", aliases = ["alias_echo"], item = u32)] - async fn sub_with_params(&self, val: u32) -> SubscriptionResult; + async fn sub_with_params(&self, val: u32); #[method(name = "params")] fn params(&self, a: u8, b: &str) -> RpcResult { @@ -115,7 +115,7 @@ mod rpc_impl { /// All head subscription #[subscription(name = "subscribeAllHeads", item = Header)] - async fn subscribe_all_heads(&self, hash: Hash) -> SubscriptionResult; + async fn subscribe_all_heads(&self, hash: Hash); } /// Trait to ensure that the trait bounds are correct. @@ -130,7 +130,7 @@ mod rpc_impl { pub trait OnlyGenericSubscription { /// Get header of a relay chain block. #[subscription(name = "sub", unsubscribe = "unsub", item = Vec)] - async fn sub(&self, hash: Input) -> SubscriptionResult; + async fn sub(&self, hash: Input); } /// Trait to ensure that the trait bounds are correct. From 109e930f08b852f5555fca40ac09d9d02edd23b8 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 3 Feb 2023 16:49:42 +0100 Subject: [PATCH 34/60] Update core/src/server/helpers.rs Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> --- core/src/server/helpers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index 270d7210b9..e3ff1336a1 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -168,7 +168,7 @@ impl<'a> MethodSinkPermit<'a> { self.send_error(id, err.into()) } - /// Send a raw JSON-RPC message to the client, `MethodSink` does not check verify the validity + /// Send a raw JSON-RPC message to the client, `MethodSink` does not check the validity /// of the JSON being sent. pub fn send_raw(self, json: String) { self.tx.send(json.clone()); From 1487383bca660032b7290624d1e16891d024178c Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 6 Feb 2023 13:43:21 +0100 Subject: [PATCH 35/60] grumbles: simplify PendingSubscription --- core/src/server/rpc_module.rs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index d34813933d..b4a56d18b8 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -49,7 +49,7 @@ use rustc_hash::FxHashMap; use serde::{de::DeserializeOwned, Serialize}; use tokio::sync::{mpsc, oneshot}; -use super::helpers::{MethodResponse, MethodSinkPermit}; +use super::helpers::MethodResponse; /// A `MethodCallback` is an RPC endpoint, callable with a standard JSON-RPC request, /// implemented as a function pointer to a `Fn` function taking four arguments: @@ -901,9 +901,9 @@ impl PendingSubscriptionSink { /// Reject the subscription call with the error from [`ErrorObject`]. pub async fn reject(self, err: impl Into) -> Result<(), SubscriptionAcceptRejectError> { let err = MethodResponse::error(self.id, err.into()); - let permit = self.inner.reserve().await.map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; + self.inner.send(err.result.clone()).await.map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; + self.subscribe.send(err).map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; - Self::answer_subscription(permit, err, self.subscribe).await?; Ok(()) } @@ -914,9 +914,8 @@ impl PendingSubscriptionSink { let response = MethodResponse::response(self.id, &self.uniq_sub.sub_id, self.inner.max_response_size() as usize); let success = response.success; - let permit = self.inner.reserve().await.map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; - - Self::answer_subscription(permit, response, self.subscribe).await?; + self.inner.send(response.result.clone()).await.map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; + self.subscribe.send(response).map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted)?; if success { let (tx, rx) = mpsc::channel(1); @@ -932,15 +931,6 @@ impl PendingSubscriptionSink { Err(SubscriptionAcceptRejectError::MessageTooLarge) } } - - async fn answer_subscription( - permit: MethodSinkPermit<'_>, - response: MethodResponse, - subscribe_call: oneshot::Sender, - ) -> Result<(), SubscriptionAcceptRejectError> { - permit.send_raw(response.result.clone()); - subscribe_call.send(response).map_err(|_| SubscriptionAcceptRejectError::RemotePeerAborted) - } } /// Represents a single subscription that hasn't been processed yet. From f468f0348f7fc954eb8b82fd27dc0f40faee41a4 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 6 Feb 2023 14:02:45 +0100 Subject: [PATCH 36/60] grumbles: fix doc nits --- proc-macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index 26984b6efa..81c0a98dfb 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -99,7 +99,7 @@ pub(crate) mod visitor; /// async fn async_method(&self, param_a: u8, param_b: String) -> u16; /// fn sync_method(&self) -> String; /// -/// // Note that `pending subscription sink` was added automatically. +/// // Note that `pending_subscription_sink` and `SubscriptionResult` were added automatically. /// async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult; /// /// fn into_rpc(self) -> Result { From 01a56796391e269ea010ded4a98be32f10bc7deb Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Mon, 6 Feb 2023 18:35:08 +0100 Subject: [PATCH 37/60] remove pipe_from_stream APIs again --- core/src/error.rs | 31 +--- core/src/lib.rs | 2 +- core/src/server/rpc_module.rs | 177 +-------------------- examples/examples/ws_pubsub_with_params.rs | 40 +++-- proc-macros/src/lib.rs | 12 +- tests/tests/helpers.rs | 138 ++++++++-------- tests/tests/integration_tests.rs | 34 ---- tests/tests/rpc_module.rs | 5 +- types/src/error.rs | 4 - 9 files changed, 106 insertions(+), 337 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index 8a80cb6acf..a72abff562 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -27,8 +27,7 @@ use std::fmt; use jsonrpsee_types::error::{ - CallError, ErrorObject, ErrorObjectOwned, CALL_EXECUTION_FAILED_CODE, INVALID_PARAMS_CODE, SUBSCRIPTION_CLOSED, - UNKNOWN_ERROR_CODE, + CallError, ErrorObject, ErrorObjectOwned, CALL_EXECUTION_FAILED_CODE, INVALID_PARAMS_CODE, UNKNOWN_ERROR_CODE, }; /// Convenience type for displaying errors. @@ -146,34 +145,6 @@ impl From for ErrorObjectOwned { } } -/// A type to represent when a subscription gets closed -/// by either the server or client side. -#[derive(Clone, Debug)] -pub enum SubscriptionClosed { - /// The remote peer closed the connection or called the unsubscribe method. - RemotePeerAborted, - /// The subscription was completed successfully by the server. - Success, - /// The subscription failed during execution by the server. - Failed(ErrorObject<'static>), -} - -impl From for ErrorObjectOwned { - fn from(err: SubscriptionClosed) -> Self { - match err { - SubscriptionClosed::RemotePeerAborted => { - ErrorObject::owned(SUBSCRIPTION_CLOSED, "Subscription was closed by the remote peer", None::<()>) - } - SubscriptionClosed::Success => ErrorObject::owned( - SUBSCRIPTION_CLOSED, - "Subscription was completed by the server successfully", - None::<()>, - ), - SubscriptionClosed::Failed(err) => err, - } - } -} - /// Generic transport error. #[derive(Debug, thiserror::Error)] pub enum GenericTransportError { diff --git a/core/src/lib.rs b/core/src/lib.rs index 63144523b4..8649782aaa 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -58,7 +58,7 @@ cfg_client! { /// Shared tracing helpers to trace RPC calls. pub mod tracing; pub use async_trait::async_trait; -pub use error::{Error, SubscriptionAcceptRejectError, SubscriptionClosed, SubscriptionEmptyError}; +pub use error::{Error, SubscriptionAcceptRejectError, SubscriptionEmptyError}; /// JSON-RPC result. pub type RpcResult = std::result::Result; diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index b4a56d18b8..a9126a764b 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -34,11 +34,10 @@ use crate::error::{Error, SubscriptionAcceptRejectError}; use crate::id_providers::RandomIntegerIdProvider; use crate::server::helpers::MethodSink; use crate::traits::{IdProvider, ToRpcParams}; -use crate::{SubscriptionClosed, SubscriptionResult}; +use crate::SubscriptionResult; use futures_util::future::Either; use futures_util::{future::BoxFuture, FutureExt}; -use futures_util::{Stream, StreamExt, TryStream, TryStreamExt}; -use jsonrpsee_types::error::{CallError, ErrorCode, ErrorObject, ErrorObjectOwned, SUBSCRIPTION_CLOSED_WITH_ERROR}; +use jsonrpsee_types::error::{CallError, ErrorCode, ErrorObject, ErrorObjectOwned}; use jsonrpsee_types::response::{SubscriptionError, SubscriptionPayloadError}; use jsonrpsee_types::{ ErrorResponse, Id, Params, Request, Response, SubscriptionId as RpcSubscriptionId, SubscriptionPayload, @@ -256,37 +255,6 @@ impl Debug for MethodKind { } } -/// A future that only resolves if a future was set. -pub enum PendingOrFuture<'a> { - /// This will never resolve. - Pending, - /// Resolves once the future is completed. - Future(BoxFuture<'a, Result<(), DisconnectError>>), -} - -impl<'a> std::fmt::Debug for PendingOrFuture<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let msg = match self { - Self::Pending => "pending", - Self::Future(_) => "future", - }; - f.write_str(msg) - } -} - -impl<'a> std::future::Future for PendingOrFuture<'a> { - type Output = Result<(), DisconnectError>; - - fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { - let this = std::pin::Pin::into_inner(self); - - match this { - Self::Pending => std::task::Poll::Pending, - Self::Future(fut) => fut.poll_unpin(cx), - } - } -} - /// Reference-counted, clone-on-write collection of synchronous and asynchronous methods. #[derive(Default, Debug, Clone)] pub struct Methods { @@ -850,7 +818,7 @@ impl RpcModule { /// // NOTE: The reason why we use `mpsc` here is because it allows `IsUnsubscribed::unsubscribed` // to be &self instead of &mut self. -#[derive(Debug)] +#[derive(Debug, Clone)] struct IsUnsubscribed(mpsc::Sender<()>); impl IsUnsubscribed { @@ -934,7 +902,7 @@ impl PendingSubscriptionSink { } /// Represents a single subscription that hasn't been processed yet. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SubscriptionSink { /// Sink. inner: MethodSink, @@ -1019,143 +987,6 @@ impl SubscriptionSink { .map_err(Into::into) } - /// Reads data from the `stream` and sends back data on the subscription - /// when items gets produced by the stream. - /// - /// The underlying sink is a bounded channel which may not have room for new items - /// if the client can't keep up with all the messages from the stream. Thus, you need to provide a closure - /// with a policy what to do when the underlying channel has not space to "buffer" more messages. - /// - /// The underlying stream must produce `Result values, see [`futures_util::TryStream`] for further information. - /// - /// Returns `Ok(())` if the stream or connection was terminated. - /// Returns `Err(_)` immediately if the underlying stream returns an error or if an item from the stream could not be serialized. - /// - /// # Examples - /// - /// ```no_run - /// - /// use jsonrpsee_core::server::rpc_module::RpcModule; - /// use jsonrpsee_core::error::{Error, SubscriptionClosed}; - /// use jsonrpsee_types::ErrorObjectOwned; - /// use anyhow::anyhow; - /// - /// let mut m = RpcModule::new(()); - /// m.register_subscription("sub", "_", "unsub", |params, pending, _| async move { - /// let mut sink = pending.accept().await?; - /// - /// let stream = futures_util::stream::iter(vec![Ok(1_u32), Ok(2), Err("error on the stream")]); - /// // This will return send `[Ok(1_u32), Ok(2_u32), Err(Error::SubscriptionClosed))]` to the subscriber - /// // because after the `Err(_)` then the stream is terminated. - /// let stream = futures_util::stream::iter(vec![Ok(1_u32), Ok(2), Err("error on the stream")]); - /// - /// // jsonrpsee doesn't send an error notification unless `close` is explicitly called. - /// // If we pipe messages to the sink, we can inspect why it ended: - /// match sink.pipe_from_try_stream(|last, next| last.unwrap_or(next), stream).await { - /// SubscriptionClosed::Success => { - /// let err_obj: ErrorObjectOwned = SubscriptionClosed::Success.into(); - /// sink.close(err_obj).await; - /// } - /// // we don't want to send close reason when the client is unsubscribed or disconnected. - /// SubscriptionClosed::RemotePeerAborted => (), - /// SubscriptionClosed::Failed(e) => { - /// sink.close(e).await; - /// } - /// } - /// Ok(()) - /// }); - /// ``` - pub async fn pipe_from_try_stream<'a, S, T, F, E>(&'a mut self, f: F, stream: S) -> SubscriptionClosed - where - F: Fn(Option>, PendingOrFuture<'a>) -> PendingOrFuture<'a>, - S: TryStream + Unpin, - T: Serialize, - E: std::fmt::Display, - { - fn err(err: impl std::fmt::Display) -> SubscriptionClosed { - let err = ErrorObject::owned(SUBSCRIPTION_CLOSED_WITH_ERROR, err.to_string(), None::<()>); - SubscriptionClosed::Failed(err) - } - - let mut pending_send = PendingOrFuture::Pending; - let closed = self.closed(); - - tokio::pin!(closed, stream); - - let mut next_item = stream.try_next(); - - loop { - match futures_util::future::select(closed, futures_util::future::select(pending_send, next_item)).await { - Either::Left((_, _)) => { - break SubscriptionClosed::RemotePeerAborted; - } - - Either::Right((Either::Left((maybe_sent, n)), c)) => { - if maybe_sent.is_err() { - break SubscriptionClosed::RemotePeerAborted; - } - pending_send = PendingOrFuture::Pending; - next_item = n; - closed = c; - } - - Either::Right((Either::Right((item, pending)), c)) => { - let item = match item { - Ok(Some(item)) => item, - Ok(None) => break SubscriptionClosed::Success, - Err(e) => break err(e), - }; - - let msg = match self.build_message(&item) { - Ok(msg) => msg, - Err(e) => break err(e), - }; - - let next: BoxFuture<_> = Box::pin(self.send(msg)); - - let pending = match pending { - PendingOrFuture::Pending => None, - fut => Some(fut), - }; - - pending_send = f(pending, PendingOrFuture::Future(next)); - closed = c; - next_item = stream.try_next(); - } - } - } - } - - /// Similar to [`SubscriptionSink::pipe_from_try_stream`] but it doesn't require the stream return `Result`. - /// - /// Warning: it's possible to pass in a stream that returns `Result` if `Result: Serialize` is satisfied - /// but it won't cancel the stream when an error occurs. If you want the stream to be canceled when an - /// error occurs use [`SubscriptionSink::pipe_from_try_stream`] instead. - /// - /// # Examples - /// - /// ```no_run - /// - /// use jsonrpsee_core::server::rpc_module::RpcModule; - /// - /// let mut m = RpcModule::new(()); - /// m.register_subscription("sub", "_", "unsub", |params, pending, _| async move { - /// let stream = futures_util::stream::iter(vec![1_usize, 2, 3]); - /// - /// let mut sink = pending.accept().await?; - /// sink.pipe_from_stream(|_last, next| next, stream).await; - /// Ok(()) - /// }); - /// ``` - pub async fn pipe_from_stream<'a, S, T, F>(&'a mut self, f: F, stream: S) -> SubscriptionClosed - where - F: Fn(Option>, PendingOrFuture<'a>) -> PendingOrFuture<'a>, - S: Stream + Unpin, - T: Serialize, - { - self.pipe_from_try_stream::<_, _, _, Error>(f, stream.map(|item| Ok(item))).await - } - fn build_error_message(&self, error: &T) -> Result { serde_json::to_string(&SubscriptionError::new( self.method.into(), diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index 4d0a64d66a..3dd9af6b47 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -27,12 +27,14 @@ use std::net::SocketAddr; use std::time::Duration; -use futures::StreamExt; +use futures::{Stream, StreamExt}; use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; -use jsonrpsee::rpc_params; +use jsonrpsee::core::server::rpc_module::TrySendError; +use jsonrpsee::core::{Serialize, SubscriptionResult}; use jsonrpsee::server::{RpcModule, ServerBuilder}; use jsonrpsee::types::ErrorObjectOwned; use jsonrpsee::ws_client::WsClientBuilder; +use jsonrpsee::{rpc_params, PendingSubscriptionSink}; use tokio::time::interval; use tokio_stream::wrappers::IntervalStream; @@ -80,10 +82,7 @@ async fn run_server() -> anyhow::Result { let interval = interval(Duration::from_millis(200)); let stream = IntervalStream::new(interval).map(move |_| item); - - let mut sink = pending.accept().await?; - - sink.pipe_from_stream(|_last, next| next, stream).await; + pipe_from_stream_and_drop(pending, stream).await?; Ok(()) }) @@ -95,10 +94,7 @@ async fn run_server() -> anyhow::Result { let item = &LETTERS[one..two]; let interval = interval(Duration::from_millis(200)); let stream = IntervalStream::new(interval).map(move |_| item); - - let mut sink = pending.accept().await?; - - sink.pipe_from_stream(|_last, next| next, stream).await; + pipe_from_stream_and_drop(pending, stream).await?; Ok(()) }) @@ -113,3 +109,27 @@ async fn run_server() -> anyhow::Result { Ok(addr) } + +async fn pipe_from_stream_and_drop(pending: PendingSubscriptionSink, mut stream: S) -> SubscriptionResult +where + S: Stream + Unpin, + T: Serialize, +{ + let mut sink = pending.accept().await?; + + loop { + tokio::select! { + _ = sink.closed() => break Ok(()), + Some(item) = stream.next() => { + let msg = sink.build_message(&item)?; + match sink.try_send(msg) { + Ok(_) => (), + Err(TrySendError::Closed(_)) => break Ok(()), + // channel is full, let's be naive an just drop the message. + Err(TrySendError::Full(_)) => break Ok(()), + } + } + else => break Ok(()), + } + } +} diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index 81c0a98dfb..ee8cf51cbb 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -295,8 +295,8 @@ pub(crate) mod visitor; /// async fn sub_override_notif_method(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { /// let mut sink = pending.accept().await?; /// -/// let stream = futures_util::stream::iter(["one", "two", "three"]); -/// sink.pipe_from_stream(|_last, next| next, stream).await; +/// let msg = sink.build_message(&"Response_A")?; +/// sink.send(msg).await?; /// /// Ok(()) /// } @@ -305,11 +305,11 @@ pub(crate) mod visitor; /// async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { /// let sink = pending.accept().await?; /// -/// let msg1 = sink.build_message(&"Response_A").unwrap(); -/// let msg2 = sink.build_message(&"Response_B").unwrap(); +/// let msg1 = sink.build_message(&"Response_A")?; +/// let msg2 = sink.build_message(&"Response_B")?; /// -/// sink.send(msg1).await.unwrap(); -/// sink.send(msg2).await.unwrap(); +/// sink.send(msg1).await?; +/// sink.send(msg2).await?; /// /// Ok(()) /// } diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index 42b621f5e0..52e64ff169 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -29,14 +29,17 @@ use std::net::SocketAddr; use std::time::Duration; -use futures::{SinkExt, StreamExt}; +use futures::future::Either; +use futures::{SinkExt, Stream, StreamExt}; use jsonrpsee::core::server::host_filtering::AllowHosts; -use jsonrpsee::core::{Error, SubscriptionClosed}; +use jsonrpsee::core::server::rpc_module::TrySendError; +use jsonrpsee::core::{Error, SubscriptionResult}; use jsonrpsee::server::middleware::proxy_get_request::ProxyGetRequestLayer; use jsonrpsee::server::{ServerBuilder, ServerHandle}; -use jsonrpsee::types::error::{ErrorObject, SUBSCRIPTION_CLOSED_WITH_ERROR}; +use jsonrpsee::types::error::ErrorObject; use jsonrpsee::types::ErrorObjectOwned; -use jsonrpsee::RpcModule; +use jsonrpsee::{PendingSubscriptionSink, RpcModule}; +use serde::Serialize; use tokio::time::interval; use tokio_stream::wrappers::IntervalStream; use tower_http::cors::CorsLayer; @@ -52,9 +55,7 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) .register_subscription("subscribe_hello", "subscribe_hello", "unsubscribe_hello", |_, pending, _| async move { let interval = interval(Duration::from_millis(50)); let stream = IntervalStream::new(interval).map(move |_| &"hello from subscription"); - - let mut sink = pending.accept().await?; - sink.pipe_from_stream(|_, next| next, stream).await; + pipe_from_stream_and_drop(pending, stream).await?; Ok(()) }) @@ -64,9 +65,7 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) .register_subscription("subscribe_foo", "subscribe_foo", "unsubscribe_foo", |_, pending, _| async { let interval = interval(Duration::from_millis(100)); let stream = IntervalStream::new(interval).map(move |_| 1337_usize); - - let mut sink = pending.accept().await?; - sink.pipe_from_stream(|_, next| next, stream).await; + pipe_from_stream_and_drop(pending, stream).await?; Ok(()) }) @@ -89,9 +88,7 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) let wrapping_counter = futures::stream::iter((count..).cycle()); let interval = interval(Duration::from_millis(100)); let stream = IntervalStream::new(interval).zip(wrapping_counter).map(move |(_, c)| c); - - let mut sink = pending.accept().await?; - sink.pipe_from_stream(|_, next| next, stream).await; + pipe_from_stream_and_drop(pending, stream).await?; Ok(()) }, @@ -102,11 +99,7 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) .register_subscription("subscribe_noop", "subscribe_noop", "unsubscribe_noop", |_, pending, _| async { let sink = pending.accept().await.unwrap(); tokio::time::sleep(Duration::from_secs(1)).await; - let err = ErrorObject::owned( - SUBSCRIPTION_CLOSED_WITH_ERROR, - "Server closed the stream because it was lazy", - None::<()>, - ); + let err = ErrorObject::owned(1, "Server closed the stream because it was lazy", None::<()>); sink.close(err).await; Ok(()) @@ -117,67 +110,13 @@ pub async fn server_with_subscription_and_handle() -> (SocketAddr, ServerHandle) .register_subscription("subscribe_5_ints", "n", "unsubscribe_5_ints", |_, pending, _| async move { let interval = interval(Duration::from_millis(50)); let stream = IntervalStream::new(interval).zip(futures::stream::iter(1..=5)).map(|(_, c)| c); - - let mut sink = pending.accept().await?; - match sink.pipe_from_stream(|_, next| next, stream).await { - SubscriptionClosed::Success => { - sink.close(SubscriptionClosed::Success).await; - } - _ => unreachable!(), - } - - Ok(()) - }) - .unwrap(); - - module - .register_subscription("can_reuse_subscription", "n", "u_can_reuse_subscription", |_, pending, _| async move { - let stream1 = IntervalStream::new(interval(Duration::from_millis(50))) - .zip(futures::stream::iter(1..=5)) - .map(|(_, c)| c); - let stream2 = IntervalStream::new(interval(Duration::from_millis(50))) - .zip(futures::stream::iter(6..=10)) - .map(|(_, c)| c); - - let mut sink = pending.accept().await?; - let result = sink.pipe_from_stream(|_, next| next, stream1).await; - assert!(matches!(result, SubscriptionClosed::Success)); - - match sink.pipe_from_stream(|_, next| next, stream2).await { - SubscriptionClosed::Success => { - sink.close(SubscriptionClosed::Success).await; - } - _ => unreachable!(), - } + tracing::info!("pipe_from_stream"); + pipe_from_stream_and_drop(pending, stream).await?; Ok(()) }) .unwrap(); - module - .register_subscription( - "subscribe_with_err_on_stream", - "n", - "unsubscribe_with_err_on_stream", - move |_, pending, _| async { - let err: &'static str = "error on the stream"; - - // Create stream that produce an error which will cancel the subscription. - let stream = futures::stream::iter(vec![Ok(1_u32), Err(err), Ok(2), Ok(3)]); - - let mut sink = pending.accept().await?; - match sink.pipe_from_try_stream(|_, next| next, stream).await { - SubscriptionClosed::Failed(e) => { - sink.close(e).await; - } - _ => unreachable!(), - }; - - Ok(()) - }, - ) - .unwrap(); - let addr = server.local_addr().unwrap(); let server_handle = server.start(module).unwrap(); @@ -230,8 +169,7 @@ pub async fn server_with_sleeping_subscription(tx: futures::channel::mpsc::Sende let interval = interval(Duration::from_secs(60 * 60)); let stream = IntervalStream::new(interval).zip(futures::stream::iter(1..=5)).map(|(_, c)| c); - let mut sink = pending.accept().await?; - sink.pipe_from_stream(|_, next| next, stream).await; + pipe_from_stream_and_drop(pending, stream).await?; let send_back = std::sync::Arc::make_mut(&mut tx); send_back.send(()).await.unwrap(); @@ -280,3 +218,51 @@ pub fn init_logger() { .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .try_init(); } + +pub async fn pipe_from_stream_and_drop( + pending: PendingSubscriptionSink, + stream: impl Stream + Unpin, +) -> SubscriptionResult { + let mut sink = pending.accept().await?; + let other = sink.clone(); + let closed = other.closed(); + + tokio::pin!(closed, stream); + + let mut rx_next = stream.next(); + + loop { + match futures::future::select(closed, rx_next).await { + Either::Left((_, _)) => { + break; + } + + Either::Right((Some(item), c)) => { + let msg = match sink.build_message(&item) { + Ok(msg) => msg, + Err(e) => { + sink.close(ErrorObject::owned(1, e.to_string(), None::<()>)).await; + return Err(e.into()); + } + }; + + match sink.try_send(msg) { + Ok(_) => (), + Err(TrySendError::Closed(_)) => break, + Err(TrySendError::Full(_)) => (), + }; + + closed = c; + rx_next = stream.next(); + } + + Either::Right((None, _)) => { + break; + } + } + } + + sink.close(ErrorObject::owned(1, "Subscription executed successful", None::<()>)).await; + + Ok(()) +} diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 103be41394..1dd2d08f0b 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -587,24 +587,6 @@ async fn ws_server_cancels_subscriptions_on_reset_conn() { assert_eq!(rx_len, 10); } -#[tokio::test] -async fn ws_server_cancels_sub_stream_after_err() { - init_logger(); - - let addr = server_with_subscription().await; - let server_url = format!("ws://{}", addr); - - let client = WsClientBuilder::default().build(&server_url).await.unwrap(); - let mut sub: Subscription = client - .subscribe("subscribe_with_err_on_stream", rpc_params![], "unsubscribe_with_err_on_stream") - .await - .unwrap(); - - assert_eq!(sub.next().await.unwrap().unwrap(), 1); - // The server closed down the subscription with the underlying error from the stream. - assert!(sub.next().await.is_none()); -} - #[tokio::test] async fn ws_server_subscribe_with_stream() { init_logger(); @@ -661,22 +643,6 @@ async fn ws_server_pipe_from_stream_should_cancel_tasks_immediately() { assert_eq!(rx_len, 10); } -#[tokio::test] -async fn ws_server_pipe_from_stream_can_be_reused() { - init_logger(); - - let addr = server_with_subscription().await; - let client = WsClientBuilder::default().build(&format!("ws://{}", addr)).await.unwrap(); - let sub = client - .subscribe::("can_reuse_subscription", rpc_params![], "u_can_reuse_subscription") - .await - .unwrap(); - - let items = sub.fold(0, |acc, _| async move { acc + 1 }).await; - - assert_eq!(items, 10); -} - #[tokio::test] async fn ws_batch_works() { init_logger(); diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index 07cd8d3264..0ec61efc04 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -30,7 +30,7 @@ use std::collections::{HashMap, VecDeque}; use std::time::Duration; use futures::StreamExt; -use helpers::init_logger; +use helpers::{init_logger, pipe_from_stream_and_drop}; use jsonrpsee::core::error::{Error, SubscriptionEmptyError}; use jsonrpsee::core::server::rpc_module::*; use jsonrpsee::core::EmptyServerParams; @@ -350,9 +350,8 @@ async fn subscribe_unsubscribe_without_server() { .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| async move { let interval = interval(Duration::from_millis(200)); let stream = IntervalStream::new(interval).map(move |_| 1); - let mut sink = pending.accept().await.unwrap(); + pipe_from_stream_and_drop(pending, stream).await?; - sink.pipe_from_stream(|_, next| next, stream).await; Ok(()) }) .unwrap(); diff --git a/types/src/error.rs b/types/src/error.rs index 59560f750d..8a57debe80 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -186,10 +186,6 @@ pub const SERVER_IS_BUSY_CODE: i32 = -32604; pub const CALL_EXECUTION_FAILED_CODE: i32 = -32000; /// Unknown error. pub const UNKNOWN_ERROR_CODE: i32 = -32001; -/// Subscription got closed by the server. -pub const SUBSCRIPTION_CLOSED: i32 = -32003; -/// Subscription got closed by the server. -pub const SUBSCRIPTION_CLOSED_WITH_ERROR: i32 = -32004; /// Batched requests are not supported by the server. pub const BATCHES_NOT_SUPPORTED_CODE: i32 = -32005; /// Subscription limit per connection was exceeded. From 62496e230e1dcaebca778746211f0ecdd673f4ef Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 7 Feb 2023 10:18:30 +0100 Subject: [PATCH 38/60] add backpressure test for ws server --- server/src/tests/ws.rs | 111 ++++++++++++++++++++++++++++++++++++++++ test-utils/src/mocks.rs | 4 ++ 2 files changed, 115 insertions(+) diff --git a/server/src/tests/ws.rs b/server/src/tests/ws.rs index 48fc7e4cb2..7cccab0d00 100644 --- a/server/src/tests/ws.rs +++ b/server/src/tests/ws.rs @@ -24,13 +24,17 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use std::collections::VecDeque; + use crate::tests::helpers::{deser_call, init_logger, server_with_context}; use crate::types::SubscriptionId; use crate::{RpcModule, ServerBuilder}; +use jsonrpsee_core::server::rpc_module::TrySendError; use jsonrpsee_core::{traits::IdProvider, Error}; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::mocks::{Id, WebSocketTestClient, WebSocketTestError}; use jsonrpsee_test_utils::TimeoutFutureExt; +use jsonrpsee_types::SubscriptionResponse; use serde_json::Value as JsonValue; use super::helpers::server; @@ -646,3 +650,110 @@ async fn batch_with_mixed_calls() { let response = client.send_request_text(req.to_string()).with_default_timeout().await.unwrap().unwrap(); assert_eq!(response, res); } + +#[tokio::test] +async fn ws_server_backpressure_works() { + init_logger(); + + let (backpressure_tx, mut backpressure_rx) = tokio::sync::mpsc::channel(1); + + let server = ServerBuilder::default() + .set_backpressure_buffer_capacity(5) + .build("127.0.0.1:0") + .with_default_timeout() + .await + .unwrap() + .unwrap(); + + let mut module = RpcModule::new(backpressure_tx); + + module + .register_subscription( + "subscribe_with_backpressure_aggregation", + "n", + "unsubscribe_with_backpressure_aggregation", + move |_, pending, mut backpressure_tx| async move { + let mut sink = pending.accept().await?; + let n = sink.build_message(&1).unwrap(); + let bp = sink.build_message(&2).unwrap(); + + let mut buf = VecDeque::new(); + buf.push_back(n.clone()); + let mut sent = 0; + + while let Some(next) = buf.pop_front() { + if sent > 20 { + break; + } + + // just try "buffer" 20 items to avoid OOM. + if buf.len() > 20 { + sink.send(next).await?; + sent += 1; + } else { + match sink.try_send(next) { + Err(TrySendError::Closed(_)) => { + println!("user closed"); + break; + } + Err(TrySendError::Full(unsent)) => { + // send a message to the testing code to indicate that + // the buffer was exceeded. + let b_tx = std::sync::Arc::make_mut(&mut backpressure_tx); + let _ = b_tx.send(()).await; + + buf.push_back(unsent); + buf.push_back(bp.clone()); + } + Ok(()) => { + sent += 1; + buf.push_back(n.clone()); + } + } + } + } + + Ok(()) + }, + ) + .unwrap(); + let addr = server.local_addr().unwrap(); + + let _server_handle = server.start(module).unwrap(); + + // Send a valid batch. + let mut client = WebSocketTestClient::new(addr).with_default_timeout().await.unwrap().unwrap(); + let req = r#" + {"jsonrpc":"2.0","method":"subscribe_with_backpressure_aggregation", "params":[],"id":1}"#; + client.send(req).with_default_timeout().await.unwrap().unwrap(); + + backpressure_rx.recv().await.unwrap(); + + let now = std::time::Instant::now(); + let mut msg; + + // Assert that first `item == 2` was sent and then + // the client start reading the socket again the buffered items should be sent. + // Thus, eventually `item == 1` should be sent again. + let mut seen_backpressure_item = false; + let mut seen_item_after_backpressure = false; + + while now.elapsed() < std::time::Duration::from_secs(10) { + msg = client.receive().with_default_timeout().await.unwrap().unwrap(); + if let Ok(sub_notif) = serde_json::from_str::>(&msg) { + match sub_notif.params.result { + 1 if seen_backpressure_item => { + seen_item_after_backpressure = true; + break; + } + 2 => { + seen_backpressure_item = true; + } + _ => (), + } + } + } + + assert!(seen_backpressure_item); + assert!(seen_item_after_backpressure); +} diff --git a/test-utils/src/mocks.rs b/test-utils/src/mocks.rs index 456e2407ed..e5bdb4a806 100644 --- a/test-utils/src/mocks.rs +++ b/test-utils/src/mocks.rs @@ -132,6 +132,10 @@ impl WebSocketTestClient { pub async fn send_request_binary(&mut self, msg: &[u8]) -> Result { self.tx.send_binary(msg).await?; self.tx.flush().await?; + self.receive().await + } + + pub async fn receive(&mut self) -> Result { let mut data = Vec::new(); self.rx.receive_data(&mut data).await?; String::from_utf8(data).map_err(Into::into) From dc37776342fb4d4a5ed396f3a68fb44adba6c96f Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 7 Feb 2023 13:26:11 +0100 Subject: [PATCH 39/60] rpc module: add `send_timeout` APIs --- core/src/server/helpers.rs | 18 ++++++---- core/src/server/rpc_module.rs | 34 ++++++++++++++++-- server/src/tests/ws.rs | 67 +++++++++++++++-------------------- 3 files changed, 71 insertions(+), 48 deletions(-) diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index e3ff1336a1..bdce1e3e18 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -25,6 +25,7 @@ // DEALINGS IN THE SOFTWARE. use std::io; +use std::time::Duration; use crate::tracing::tx_log_from_str; use crate::Error; @@ -33,7 +34,7 @@ use jsonrpsee_types::{Id, InvalidRequest, Response}; use serde::Serialize; use tokio::sync::mpsc::{self, Permit}; -use super::rpc_module::{DisconnectError, SubscriptionMessage, TrySendError}; +use super::rpc_module::{DisconnectError, SendTimeoutError, SubscriptionMessage, TrySendError}; /// Bounded writer that allows writing at most `max_len` bytes. /// @@ -121,22 +122,25 @@ impl MethodSink { self.max_response_size } - /// Non-blocking send which fails if the channel is closed or full + /// Attempts to send out the message immediately and fails if the underlying + /// connection has been closed or if the message buffer is full. /// /// Returns the message if the send fails such that either can be thrown away or re-sent later. pub fn try_send(&mut self, msg: String) -> Result<(), TrySendError> { tx_log_from_str(&msg, self.max_log_length); - self.tx.try_send(msg)?; - - Ok(()) + self.tx.try_send(msg).map_err(Into::into) } /// Async send which will wait until there is space in channel buffer or that the subscription is disconnected. pub async fn send(&self, msg: String) -> Result<(), DisconnectError> { tx_log_from_str(&msg, self.max_log_length); - self.tx.send(msg).await?; + self.tx.send(msg).await.map_err(Into::into) + } - Ok(()) + /// Similar to to `MethodSink::send` but only waits for a limited time. + pub async fn send_timeout(&self, msg: String, timeout: Duration) -> Result<(), SendTimeoutError> { + tx_log_from_str(&msg, self.max_log_length); + self.tx.send_timeout(msg, timeout).await.map_err(Into::into) } /// Waits for channel capacity. Once capacity to send one message is available, it is reserved for the caller. diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index a9126a764b..060a3fee24 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -29,6 +29,7 @@ use std::fmt::{self, Debug}; use std::future::Future; use std::ops::{Deref, DerefMut}; use std::sync::Arc; +use std::time::Duration; use crate::error::{Error, SubscriptionAcceptRejectError}; use crate::id_providers::RandomIntegerIdProvider; @@ -71,7 +72,7 @@ pub type ConnectionId = usize; /// Max response size. pub type MaxResponseSize = usize; -/// TrySendError +/// Error that may occur during `SubscriptionSink::try_send`. #[derive(Debug)] pub enum TrySendError { /// The channel is closed. @@ -92,10 +93,20 @@ pub enum SubscriptionAnswered { No(MethodResponse), } -/// Disconnect error +/// Error that may occur during `MethodSink::send` or `SubscriptionSink::send`. #[derive(Debug)] pub struct DisconnectError(pub SubscriptionMessage); +/// Error that may occur during `SubscriptionSink::send_timeout`. +#[derive(Debug)] +pub enum SendTimeoutError { + /// The data could not be sent because the timeout elapsed + /// which most likely is that the channel is full. + Timeout(SubscriptionMessage), + /// The channel is full. + Closed(SubscriptionMessage), +} + /// Helper struct to manage subscriptions. pub struct ConnState<'a> { /// Connection ID @@ -128,6 +139,15 @@ impl From> for TrySendError { } } +impl From> for SendTimeoutError { + fn from(e: mpsc::error::SendTimeoutError) -> Self { + match e { + mpsc::error::SendTimeoutError::Closed(m) => Self::Closed(SubscriptionMessage(m)), + mpsc::error::SendTimeoutError::Timeout(m) => Self::Timeout(SubscriptionMessage(m)), + } + } +} + impl<'a> std::fmt::Debug for ConnState<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ConnState").field("conn_id", &self.conn_id).finish() @@ -946,6 +966,16 @@ impl SubscriptionSink { self.inner.send(msg.0).await.map_err(Into::into) } + /// Similar to to `SubscriptionSink::send` but only waits for a limited time. + pub async fn send_timeout(&self, msg: SubscriptionMessage, timeout: Duration) -> Result<(), SendTimeoutError> { + // Only possible to trigger when the connection is dropped. + if self.is_closed() { + return Err(SendTimeoutError::Closed(msg)); + } + + self.inner.send_timeout(msg.0, timeout).await.map_err(Into::into) + } + /// Attempts to immediately send out the message as JSON string to the subscribers but fails if the /// channel is full or the connection/subscription is closed /// diff --git a/server/src/tests/ws.rs b/server/src/tests/ws.rs index 7cccab0d00..89b568590a 100644 --- a/server/src/tests/ws.rs +++ b/server/src/tests/ws.rs @@ -24,12 +24,10 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use std::collections::VecDeque; - use crate::tests::helpers::{deser_call, init_logger, server_with_context}; use crate::types::SubscriptionId; use crate::{RpcModule, ServerBuilder}; -use jsonrpsee_core::server::rpc_module::TrySendError; +use jsonrpsee_core::server::rpc_module::SendTimeoutError; use jsonrpsee_core::{traits::IdProvider, Error}; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::mocks::{Id, WebSocketTestClient, WebSocketTestError}; @@ -655,7 +653,7 @@ async fn batch_with_mixed_calls() { async fn ws_server_backpressure_works() { init_logger(); - let (backpressure_tx, mut backpressure_rx) = tokio::sync::mpsc::channel(1); + let (backpressure_tx, mut backpressure_rx) = tokio::sync::mpsc::channel::<()>(1); let server = ServerBuilder::default() .set_backpressure_buffer_capacity(5) @@ -673,46 +671,36 @@ async fn ws_server_backpressure_works() { "n", "unsubscribe_with_backpressure_aggregation", move |_, pending, mut backpressure_tx| async move { - let mut sink = pending.accept().await?; + let sink = pending.accept().await?; let n = sink.build_message(&1).unwrap(); let bp = sink.build_message(&2).unwrap(); - let mut buf = VecDeque::new(); - buf.push_back(n.clone()); - let mut sent = 0; - - while let Some(next) = buf.pop_front() { - if sent > 20 { - break; - } - - // just try "buffer" 20 items to avoid OOM. - if buf.len() > 20 { - sink.send(next).await?; - sent += 1; - } else { - match sink.try_send(next) { - Err(TrySendError::Closed(_)) => { - println!("user closed"); - break; - } - Err(TrySendError::Full(unsent)) => { - // send a message to the testing code to indicate that - // the buffer was exceeded. - let b_tx = std::sync::Arc::make_mut(&mut backpressure_tx); - let _ = b_tx.send(()).await; - - buf.push_back(unsent); - buf.push_back(bp.clone()); - } - Ok(()) => { - sent += 1; - buf.push_back(n.clone()); - } - } + let mut msg = n.clone(); + + loop { + tokio::select! { + biased; + _ = sink.closed() => { + // User closed connection. + break; + }, + res = sink.send_timeout(msg.clone(), std::time::Duration::from_millis(100)) => { + match res { + // msg == 1 + Ok(_) => { + msg = n.clone(); + } + Err(SendTimeoutError::Closed(_)) => break, + // msg == 2 + Err(SendTimeoutError::Timeout(_)) => { + let b_tx = std::sync::Arc::make_mut(&mut backpressure_tx); + let _ = b_tx.send(()).await; + msg = bp.clone(); + } + }; + }, } } - Ok(()) }, ) @@ -740,6 +728,7 @@ async fn ws_server_backpressure_works() { while now.elapsed() < std::time::Duration::from_secs(10) { msg = client.receive().with_default_timeout().await.unwrap().unwrap(); + tracing::info!("{msg}"); if let Ok(sub_notif) = serde_json::from_str::>(&msg) { match sub_notif.params.result { 1 if seen_backpressure_item => { From 3c98441d36603f4259d111e07ffe1b7d08a8ae64 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 7 Feb 2023 13:57:20 +0100 Subject: [PATCH 40/60] rpc module: add tokio/time --- core/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Cargo.toml b/core/Cargo.toml index 5dd0b8f3c7..257f3d9ada 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -45,6 +45,7 @@ server = [ "tokio/rt", "tokio/sync", "tokio/macros", + "tokio/time", ] client = ["futures-util/sink", "tokio/sync"] async-client = [ From 8e68156a84097d8a71e5176f2ba0a40fdf96caba Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 7 Feb 2023 16:03:03 +0100 Subject: [PATCH 41/60] cleanup --- examples/examples/ws_pubsub_with_params.rs | 29 +++++++++++++++----- tests/tests/helpers.rs | 32 +++++++--------------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index 3dd9af6b47..49a0177536 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -32,7 +32,7 @@ use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; use jsonrpsee::core::server::rpc_module::TrySendError; use jsonrpsee::core::{Serialize, SubscriptionResult}; use jsonrpsee::server::{RpcModule, ServerBuilder}; -use jsonrpsee::types::ErrorObjectOwned; +use jsonrpsee::types::{ErrorObject, ErrorObjectOwned}; use jsonrpsee::ws_client::WsClientBuilder; use jsonrpsee::{rpc_params, PendingSubscriptionSink}; use tokio::time::interval; @@ -82,6 +82,7 @@ async fn run_server() -> anyhow::Result { let interval = interval(Duration::from_millis(200)); let stream = IntervalStream::new(interval).map(move |_| item); + pipe_from_stream_and_drop(pending, stream).await?; Ok(()) @@ -119,17 +120,31 @@ where loop { tokio::select! { - _ = sink.closed() => break Ok(()), - Some(item) = stream.next() => { - let msg = sink.build_message(&item)?; + _ = sink.closed() => break, + maybe_item = stream.next() => { + let item = match maybe_item { + Some(item) => item, + None => break, + }; + let msg = match sink.build_message(&item) { + Ok(msg) => msg, + Err(e) => { + sink.close(ErrorObject::owned(1, e.to_string(), None::<()>)).await; + return Err(e.into()); + } + }; + match sink.try_send(msg) { Ok(_) => (), - Err(TrySendError::Closed(_)) => break Ok(()), + Err(TrySendError::Closed(_)) => break, // channel is full, let's be naive an just drop the message. - Err(TrySendError::Full(_)) => break Ok(()), + Err(TrySendError::Full(_)) => (), } } - else => break Ok(()), } } + + sink.close(ErrorObject::owned(1, "Ok", None::<()>)).await; + + Ok(()) } diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index 52e64ff169..fba54f443d 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -29,7 +29,6 @@ use std::net::SocketAddr; use std::time::Duration; -use futures::future::Either; use futures::{SinkExt, Stream, StreamExt}; use jsonrpsee::core::server::host_filtering::AllowHosts; use jsonrpsee::core::server::rpc_module::TrySendError; @@ -221,23 +220,18 @@ pub fn init_logger() { pub async fn pipe_from_stream_and_drop( pending: PendingSubscriptionSink, - stream: impl Stream + Unpin, + mut stream: impl Stream + Unpin, ) -> SubscriptionResult { let mut sink = pending.accept().await?; - let other = sink.clone(); - let closed = other.closed(); - - tokio::pin!(closed, stream); - - let mut rx_next = stream.next(); loop { - match futures::future::select(closed, rx_next).await { - Either::Left((_, _)) => { - break; - } - - Either::Right((Some(item), c)) => { + tokio::select! { + _ = sink.closed() => break, + maybe_item = stream.next() => { + let item = match maybe_item { + Some(item) => item, + None => break, + }; let msg = match sink.build_message(&item) { Ok(msg) => msg, Err(e) => { @@ -249,15 +243,9 @@ pub async fn pipe_from_stream_and_drop( match sink.try_send(msg) { Ok(_) => (), Err(TrySendError::Closed(_)) => break, + // channel is full, let's be naive an just drop the message. Err(TrySendError::Full(_)) => (), - }; - - closed = c; - rx_next = stream.next(); - } - - Either::Right((None, _)) => { - break; + } } } } From 02262583f9bf72f139a00da496a790e7141e3f79 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 7 Feb 2023 16:06:44 +0100 Subject: [PATCH 42/60] Update examples/Cargo.toml --- examples/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 8fab1c9d03..a69d893d07 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -18,5 +18,4 @@ serde_json = { version = "1" } tower-http = { version = "0.3.4", features = ["full"] } tower = { version = "0.4.13", features = ["full"] } hyper = "0.14.20" -console-subscriber = "0.1.8" -bounded-vec-deque = "0.1.1" \ No newline at end of file +console-subscriber = "0.1.8" \ No newline at end of file From 2024cad6933592ab36f1fe3b5edc1201a93e40dd Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 7 Feb 2023 16:10:40 +0100 Subject: [PATCH 43/60] Update server/src/server.rs --- server/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server.rs b/server/src/server.rs index 3eb012d676..b5b097660e 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -423,7 +423,7 @@ impl Builder { /// The server enforces backpressure which means that /// `n` messages can be buffered and if the client - /// can't keep with the server. + /// can't keep with up the server. /// /// This `capacity` is applied per connection and /// applies globally on the connection which implies From 898fcd3cbfe6a637b75701a30de6b93ee20a3b6b Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 7 Feb 2023 16:13:21 +0100 Subject: [PATCH 44/60] Update server/src/server.rs --- server/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server.rs b/server/src/server.rs index b5b097660e..4f93320371 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -433,7 +433,7 @@ impl Builder { /// and the client can't keep up then no new messages are handled. /// /// If this limit is exceeded the server will "back-off" - /// and only accept ones the client reads pending messages. + /// and only accept new messages once the client reads pending messages. /// /// # Panics /// From d3065939a314fb4d962efb61ef668c71e0c447d1 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 7 Feb 2023 18:37:16 +0100 Subject: [PATCH 45/60] Update server/src/server.rs --- server/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/server.rs b/server/src/server.rs index 4f93320371..b902c360d7 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -432,7 +432,7 @@ impl Builder { /// For example if a subscription produces plenty of new items /// and the client can't keep up then no new messages are handled. /// - /// If this limit is exceeded the server will "back-off" + /// If this limit is exceeded then the server will "back-off" /// and only accept new messages once the client reads pending messages. /// /// # Panics From 5a03761db90bcb0777896472a2f3c5e86aa334dd Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 7 Feb 2023 18:37:31 +0100 Subject: [PATCH 46/60] extract `build_message` to `SubscriptionMessage` --- benches/helpers.rs | 6 +- core/src/server/helpers.rs | 2 +- core/src/server/rpc_module.rs | 86 ++++++++++++++------- examples/examples/proc_macro.rs | 3 +- examples/examples/ws_pubsub_broadcast.rs | 81 +++++++------------ examples/examples/ws_pubsub_with_params.rs | 4 +- jsonrpsee/src/lib.rs | 2 +- proc-macros/src/lib.rs | 8 +- proc-macros/tests/ui/correct/basic.rs | 10 +-- proc-macros/tests/ui/correct/only_server.rs | 6 +- server/src/lib.rs | 5 +- server/src/tests/ws.rs | 6 +- tests/tests/helpers.rs | 4 +- tests/tests/integration_tests.rs | 3 +- tests/tests/proc_macros.rs | 9 ++- tests/tests/rpc_module.rs | 8 +- 16 files changed, 125 insertions(+), 118 deletions(-) diff --git a/benches/helpers.rs b/benches/helpers.rs index 9940b17718..1ed7bc80d3 100644 --- a/benches/helpers.rs +++ b/benches/helpers.rs @@ -132,7 +132,7 @@ pub async fn http_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee:: /// Run jsonrpsee WebSocket server for benchmarks. #[cfg(not(feature = "jsonrpc-crate"))] pub async fn ws_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee::server::ServerHandle) { - use jsonrpsee::server::ServerBuilder; + use jsonrpsee::{core::server::rpc_module::SubscriptionMessage, server::ServerBuilder}; let server = ServerBuilder::default() .max_request_body_size(u32::MAX) @@ -151,10 +151,8 @@ pub async fn ws_server(handle: tokio::runtime::Handle) -> (String, jsonrpsee::se SUB_METHOD_NAME, UNSUB_METHOD_NAME, |_params, pending, _ctx| async move { - let x = "Hello"; - let sink = pending.accept().await?; - let msg = sink.build_message(&x)?; + let msg = SubscriptionMessage::from_json(&"Hello")?; sink.send(msg).await?; Ok(()) }, diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index bdce1e3e18..397020a3e1 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -147,7 +147,7 @@ impl MethodSink { pub async fn reserve(&self) -> Result { match self.tx.reserve().await { Ok(permit) => Ok(MethodSinkPermit { tx: permit, max_log_length: self.max_log_length }), - Err(_) => Err(DisconnectError(SubscriptionMessage(String::new()))), + Err(_) => Err(DisconnectError(SubscriptionMessage::empty())), } } } diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 060a3fee24..88c08c6d3b 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -41,8 +41,7 @@ use futures_util::{future::BoxFuture, FutureExt}; use jsonrpsee_types::error::{CallError, ErrorCode, ErrorObject, ErrorObjectOwned}; use jsonrpsee_types::response::{SubscriptionError, SubscriptionPayloadError}; use jsonrpsee_types::{ - ErrorResponse, Id, Params, Request, Response, SubscriptionId as RpcSubscriptionId, SubscriptionPayload, - SubscriptionResponse, + ErrorResponse, Id, Params, Request, Response, SubscriptionId as RpcSubscriptionId, SubscriptionResponse, }; use parking_lot::Mutex; use rustc_hash::FxHashMap; @@ -126,15 +125,15 @@ pub enum InnerSubscriptionResult { impl From> for DisconnectError { fn from(e: mpsc::error::SendError) -> Self { - DisconnectError(SubscriptionMessage(e.0)) + DisconnectError(SubscriptionMessage::from_complete_message(e.0)) } } impl From> for TrySendError { fn from(e: mpsc::error::TrySendError) -> Self { match e { - mpsc::error::TrySendError::Closed(m) => Self::Closed(SubscriptionMessage(m)), - mpsc::error::TrySendError::Full(m) => Self::Full(SubscriptionMessage(m)), + mpsc::error::TrySendError::Closed(m) => Self::Closed(SubscriptionMessage::from_complete_message(m)), + mpsc::error::TrySendError::Full(m) => Self::Full(SubscriptionMessage::from_complete_message(m)), } } } @@ -142,8 +141,8 @@ impl From> for TrySendError { impl From> for SendTimeoutError { fn from(e: mpsc::error::SendTimeoutError) -> Self { match e { - mpsc::error::SendTimeoutError::Closed(m) => Self::Closed(SubscriptionMessage(m)), - mpsc::error::SendTimeoutError::Timeout(m) => Self::Timeout(SubscriptionMessage(m)), + mpsc::error::SendTimeoutError::Closed(m) => Self::Closed(SubscriptionMessage::from_complete_message(m)), + mpsc::error::SendTimeoutError::Timeout(m) => Self::Timeout(SubscriptionMessage::from_complete_message(m)), } } } @@ -194,9 +193,33 @@ impl CallResponse { } } +/// A complete subscription message or partial subscription message. +#[derive(Debug, Clone)] +pub enum SubscriptionMessageInner { + /// Complete JSON message. + Complete(String), + /// Need subscription ID and method name. + NeedsData(String), +} + /// Subscription message. #[derive(Debug, Clone)] -pub struct SubscriptionMessage(pub(crate) String); +pub struct SubscriptionMessage(pub(crate) SubscriptionMessageInner); + +impl SubscriptionMessage { + /// Create a new subscription message from JSON. + pub fn from_json(t: &impl Serialize) -> Result { + serde_json::to_string(t).map(|json| SubscriptionMessage(SubscriptionMessageInner::NeedsData(json))) + } + + pub(crate) fn from_complete_message(msg: String) -> Self { + SubscriptionMessage(SubscriptionMessageInner::Complete(msg)) + } + + pub(crate) fn empty() -> Self { + Self::from_complete_message(String::new()) + } +} /// Represent a unique subscription entry based on [`RpcSubscriptionId`] and [`ConnectionId`]. #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -400,7 +423,7 @@ impl Methods { /// ``` /// #[tokio::main] /// async fn main() { - /// use jsonrpsee::RpcModule; + /// use jsonrpsee::{RpcModule, SubscriptionMessage}; /// use jsonrpsee::types::Response; /// use futures_util::StreamExt; /// @@ -408,7 +431,7 @@ impl Methods { /// /// module.register_subscription("hi", "hi", "goodbye", |_, pending, _| async move { /// let sink = pending.accept().await?; - /// let msg = sink.build_message(&"one answer").unwrap(); + /// let msg = SubscriptionMessage::from_json(&"one answer").unwrap(); /// sink.send(msg).await.unwrap(); /// /// Ok(()) @@ -473,13 +496,13 @@ impl Methods { /// ``` /// #[tokio::main] /// async fn main() { - /// use jsonrpsee::{RpcModule, core::EmptyServerParams}; + /// use jsonrpsee::{RpcModule, core::EmptyServerParams, SubscriptionMessage}; /// /// let mut module = RpcModule::new(()); /// module.register_subscription("hi", "hi", "goodbye", |_, pending, _| async move { /// let sink = pending.accept().await?; /// - /// let msg = sink.build_message(&"one answer")?; + /// let msg = SubscriptionMessage::from_json(&"one answer")?; /// sink.send(msg).await?; /// Ok(()) /// @@ -693,7 +716,7 @@ impl RpcModule { /// /// ```no_run /// - /// use jsonrpsee_core::server::rpc_module::{RpcModule, SubscriptionSink}; + /// use jsonrpsee_core::server::rpc_module::{RpcModule, SubscriptionSink, SubscriptionMessage}; /// use jsonrpsee_core::Error; /// /// let mut ctx = RpcModule::new(99_usize); @@ -704,7 +727,7 @@ impl RpcModule { /// let sink = pending.accept().await?; /// /// let sum = x + (*ctx); - /// let msg = sink.build_message(&sum)?; + /// let msg = SubscriptionMessage::from_json(&sum)?; /// sink.send(msg).await?; /// /// Ok(()) @@ -963,7 +986,8 @@ impl SubscriptionSink { return Err(DisconnectError(msg)); } - self.inner.send(msg.0).await.map_err(Into::into) + let json = self.sub_message_to_json(msg); + self.inner.send(json).await.map_err(Into::into) } /// Similar to to `SubscriptionSink::send` but only waits for a limited time. @@ -973,7 +997,8 @@ impl SubscriptionSink { return Err(SendTimeoutError::Closed(msg)); } - self.inner.send_timeout(msg.0, timeout).await.map_err(Into::into) + let json = self.sub_message_to_json(msg); + self.inner.send_timeout(json, timeout).await.map_err(Into::into) } /// Attempts to immediately send out the message as JSON string to the subscribers but fails if the @@ -988,7 +1013,8 @@ impl SubscriptionSink { return Err(TrySendError::Closed(msg)); } - self.inner.try_send(msg.0).map_err(Into::into) + let json = self.sub_message_to_json(msg); + self.inner.try_send(json).map_err(Into::into) } /// Returns whether the subscription is closed. @@ -1005,24 +1031,24 @@ impl SubscriptionSink { } } - /// Build message that will be serialized as JSON-RPC notification. - /// - /// You need to call this method prior to send out a subscription notification. - pub fn build_message(&self, result: &T) -> Result { - serde_json::to_string(&SubscriptionResponse::new( - self.method.into(), - SubscriptionPayload { subscription: self.uniq_sub.sub_id.clone(), result }, - )) - .map(SubscriptionMessage) - .map_err(Into::into) + fn sub_message_to_json(&self, msg: SubscriptionMessage) -> String { + match msg.0 { + SubscriptionMessageInner::Complete(msg) => msg, + SubscriptionMessageInner::NeedsData(result) => { + let sub_id = serde_json::to_string(&self.uniq_sub.sub_id).expect("valid JSON; qed"); + let method = self.method; + format!( + r#"{{"jsonrpc":"2.0","method":"{method}","params":{{"subscription":{sub_id},"result":{result}}}}}"#, + ) + } + } } - fn build_error_message(&self, error: &T) -> Result { + fn build_error_message(&self, error: &T) -> Result { serde_json::to_string(&SubscriptionError::new( self.method.into(), SubscriptionPayloadError { subscription: self.uniq_sub.sub_id.clone(), error }, )) - .map(SubscriptionMessage) .map_err(Into::into) } @@ -1054,7 +1080,7 @@ impl SubscriptionSink { let msg = self.build_error_message(&err.into()).expect("valid json infallible; qed"); return Either::Right(async move { - let _ = sink.send(msg.0).await; + let _ = sink.send(msg).await; }); } } diff --git a/examples/examples/proc_macro.rs b/examples/examples/proc_macro.rs index 2b261b6c5a..165f612139 100644 --- a/examples/examples/proc_macro.rs +++ b/examples/examples/proc_macro.rs @@ -26,6 +26,7 @@ use std::net::SocketAddr; +use jsonrpsee::core::server::rpc_module::SubscriptionMessage; use jsonrpsee::core::{async_trait, client::Subscription, Error, SubscriptionResult}; use jsonrpsee::proc_macros::rpc; use jsonrpsee::server::{PendingSubscriptionSink, ServerBuilder}; @@ -67,7 +68,7 @@ impl RpcServer for RpcServerImpl { _keys: Option>, ) -> SubscriptionResult { let sink = pending.accept().await?; - let msg = sink.build_message(&vec![[0; 32]])?; + let msg = SubscriptionMessage::from_json(&vec![[0; 32]])?; sink.send(msg).await?; Ok(()) } diff --git a/examples/examples/ws_pubsub_broadcast.rs b/examples/examples/ws_pubsub_broadcast.rs index 3f2f9d2bd8..6e548dcdb3 100644 --- a/examples/examples/ws_pubsub_broadcast.rs +++ b/examples/examples/ws_pubsub_broadcast.rs @@ -28,19 +28,18 @@ use std::net::SocketAddr; -use futures::future; +use futures::future::{self, Either}; use futures::StreamExt; use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; -use jsonrpsee::core::server::rpc_module::TrySendError; +use jsonrpsee::core::server::rpc_module::SubscriptionMessage; + use jsonrpsee::core::SubscriptionResult; use jsonrpsee::rpc_params; use jsonrpsee::server::{RpcModule, ServerBuilder}; use jsonrpsee::ws_client::WsClientBuilder; use jsonrpsee::PendingSubscriptionSink; use tokio::sync::broadcast; -use tokio::time::interval; use tokio_stream::wrappers::BroadcastStream; -use tokio_stream::wrappers::IntervalStream; const NUM_SUBSCRIPTION_RESPONSES: usize = 5; @@ -97,62 +96,40 @@ async fn run_server() -> anyhow::Result { async fn pipe_from_stream_with_bounded_buffer( pending: PendingSubscriptionSink, - mut stream: BroadcastStream, + stream: BroadcastStream, ) -> SubscriptionResult { - let mut sink = pending.accept().await?; - let mut bounded_buffer = bounded_vec_deque::BoundedVecDeque::new(10); + let sink = pending.accept().await?; + let closed = sink.closed(); - // This is not recommended approach it would be better to have a queue that returns a future once - // a element is ready to consumed in the bounded_buffer. This might waste CPU with the timeouts - // as it's no guarantee that buffer has elements. - let mut timeout = IntervalStream::new(interval(std::time::Duration::from_millis(100))); + futures::pin_mut!(closed, stream); loop { - tokio::select! { - // subscription was closed, no point of reading more messages from the stream. - _ = sink.closed() => { - return Ok(()); - } - // stream yielded a new element, try to send it. - Some(Ok(v)) = stream.next() => { - let msg = sink.build_message(&v).expect("usize serialize is infalliable; qed"); - match sink.try_send(msg) { - // message was sent successfully. - Ok(()) => (), - // the connection is closed. - Err(TrySendError::Closed(_msg_unset_dont_care)) => { - tracing::warn!("The connection closed; closing subscription"); - return Ok(()); - } - // the channel was full let's buffer the ten most recent messages. - Err(TrySendError::Full(unsent_msg)) => { - if let Some(msg) = bounded_buffer.push_back(unsent_msg) { - tracing::warn!("Dropping the oldest message: {:?}", msg); - } - } - }; - } - // try to pop to oldest element for our bounded buffer and send it. - _ = timeout.next() => { - if let Some(msg) = bounded_buffer.pop_front() { - match sink.try_send(msg) { - // message was sent successfully. - Ok(()) => (), - // the connection is closed. - Err(TrySendError::Closed(_msg_unset_dont_care)) => { - tracing::warn!("The connection closed; closing subscription"); - return Ok(()); - } - // the channel was full, let's insert back the pop:ed element. - Err(TrySendError::Full(unsent_msg)) => { - bounded_buffer.push_front(unsent_msg).expect("pop:ed an element must be space in; qed"); - } - }; + match future::select(closed, stream.next()).await { + // subscription closed. + Either::Left((_, _)) => break, + + // received new item from the stream. + Either::Right((Some(Ok(item)), c)) => { + let notif = SubscriptionMessage::from_json(&item)?; + + // NOTE: this will block until there a spot in the queue + // and you might want to something smarter if it's + // critical that newer must be sent when they are produced. + if sink.send(notif).await.is_err() { + break; } + + closed = c; + } + + // stream is closed or some error, just quit. + Either::Right((_, _)) => { + break; } - else => return Ok(()), } } + + Ok(()) } // Naive example that broadcasts the produced values to all active subscribers. diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index 49a0177536..a33e772f45 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -29,7 +29,7 @@ use std::time::Duration; use futures::{Stream, StreamExt}; use jsonrpsee::core::client::{Subscription, SubscriptionClientT}; -use jsonrpsee::core::server::rpc_module::TrySendError; +use jsonrpsee::core::server::rpc_module::{SubscriptionMessage, TrySendError}; use jsonrpsee::core::{Serialize, SubscriptionResult}; use jsonrpsee::server::{RpcModule, ServerBuilder}; use jsonrpsee::types::{ErrorObject, ErrorObjectOwned}; @@ -126,7 +126,7 @@ where Some(item) => item, None => break, }; - let msg = match sink.build_message(&item) { + let msg = match SubscriptionMessage::from_json(&item) { Ok(msg) => msg, Err(e) => { sink.close(ErrorObject::owned(1, e.to_string(), None::<()>)).await; diff --git a/jsonrpsee/src/lib.rs b/jsonrpsee/src/lib.rs index 47ef9baad4..5f44308047 100644 --- a/jsonrpsee/src/lib.rs +++ b/jsonrpsee/src/lib.rs @@ -90,7 +90,7 @@ cfg_types! { } cfg_server! { - pub use jsonrpsee_core::server::rpc_module::{RpcModule, SubscriptionSink, PendingSubscriptionSink}; + pub use jsonrpsee_core::server::rpc_module::{RpcModule, SubscriptionSink, PendingSubscriptionSink, SubscriptionMessage, DisconnectError, TrySendError, SendTimeoutError}; pub use tokio; } diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index ee8cf51cbb..e9bdcb4f6b 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -219,7 +219,7 @@ pub(crate) mod visitor; /// /// // RPC is put into a separate module to clearly show names of generated entities. /// mod rpc_impl { -/// use jsonrpsee::{proc_macros::rpc, server::PendingSubscriptionSink}; +/// use jsonrpsee::{proc_macros::rpc, server::PendingSubscriptionSink, server::SubscriptionMessage}; /// use jsonrpsee::core::{async_trait, SubscriptionResult, RpcResult}; /// /// // Generate both server and client implementations, prepend all the methods with `foo_` prefix. @@ -295,7 +295,7 @@ pub(crate) mod visitor; /// async fn sub_override_notif_method(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { /// let mut sink = pending.accept().await?; /// -/// let msg = sink.build_message(&"Response_A")?; +/// let msg = SubscriptionMessage::from_json(&"Response_A")?; /// sink.send(msg).await?; /// /// Ok(()) @@ -305,8 +305,8 @@ pub(crate) mod visitor; /// async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { /// let sink = pending.accept().await?; /// -/// let msg1 = sink.build_message(&"Response_A")?; -/// let msg2 = sink.build_message(&"Response_B")?; +/// let msg1 = SubscriptionMessage::from_json(&"Response_A")?; +/// let msg2 = SubscriptionMessage::from_json(&"Response_B")?; /// /// sink.send(msg1).await?; /// sink.send(msg2).await?; diff --git a/proc-macros/tests/ui/correct/basic.rs b/proc-macros/tests/ui/correct/basic.rs index f58eb259aa..0365e30de9 100644 --- a/proc-macros/tests/ui/correct/basic.rs +++ b/proc-macros/tests/ui/correct/basic.rs @@ -6,7 +6,7 @@ use jsonrpsee::core::params::ArrayParams; use jsonrpsee::core::SubscriptionResult; use jsonrpsee::core::{async_trait, client::ClientT, RpcResult}; use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::ServerBuilder; +use jsonrpsee::server::{ServerBuilder, SubscriptionMessage}; use jsonrpsee::ws_client::*; use jsonrpsee::{rpc_params, PendingSubscriptionSink}; @@ -68,8 +68,8 @@ impl RpcServer for RpcServerImpl { async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { let sink = pending.accept().await?; - let msg1 = sink.build_message(&"Response_A").unwrap(); - let msg2 = sink.build_message(&"Response_B").unwrap(); + let msg1 = SubscriptionMessage::from_json(&"Response_A").unwrap(); + let msg2 = SubscriptionMessage::from_json(&"Response_B").unwrap(); sink.send(msg1).await.unwrap(); sink.send(msg2).await.unwrap(); @@ -80,7 +80,7 @@ impl RpcServer for RpcServerImpl { async fn sub_with_params(&self, pending: PendingSubscriptionSink, val: u32) -> SubscriptionResult { let sink = pending.accept().await?; - let msg = sink.build_message(&val).unwrap(); + let msg = SubscriptionMessage::from_json(&val).unwrap(); sink.send(msg.clone()).await.unwrap(); sink.send(msg).await.unwrap(); @@ -90,7 +90,7 @@ impl RpcServer for RpcServerImpl { async fn sub_with_override_notif_method(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { let sink = pending.accept().await?; - let msg = sink.build_message(&1).unwrap(); + let msg = SubscriptionMessage::from_json(&1).unwrap(); sink.send(msg).await.unwrap(); Ok(()) diff --git a/proc-macros/tests/ui/correct/only_server.rs b/proc-macros/tests/ui/correct/only_server.rs index 4422b293d6..89fa961d52 100644 --- a/proc-macros/tests/ui/correct/only_server.rs +++ b/proc-macros/tests/ui/correct/only_server.rs @@ -2,7 +2,7 @@ use std::net::SocketAddr; use jsonrpsee::core::{async_trait, RpcResult, SubscriptionResult}; use jsonrpsee::proc_macros::rpc; -use jsonrpsee::server::{PendingSubscriptionSink, ServerBuilder}; +use jsonrpsee::server::{PendingSubscriptionSink, ServerBuilder, SubscriptionMessage}; #[rpc(server)] pub trait Rpc { @@ -31,8 +31,8 @@ impl RpcServer for RpcServerImpl { async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { let sink = pending.accept().await?; - let msg1 = sink.build_message(&"Response_A").unwrap(); - let msg2 = sink.build_message(&"Response_B").unwrap(); + let msg1 = SubscriptionMessage::from_json(&"Response_A").unwrap(); + let msg2 = SubscriptionMessage::from_json(&"Response_B").unwrap(); sink.send(msg1).await.unwrap(); sink.send(msg2).await.unwrap(); diff --git a/server/src/lib.rs b/server/src/lib.rs index 415452eb2b..1100f89ff7 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -43,7 +43,10 @@ mod tests; pub use future::ServerHandle; pub use jsonrpsee_core::server::host_filtering::AllowHosts; -pub use jsonrpsee_core::server::rpc_module::{PendingSubscriptionSink, RpcModule, SubscriptionSink}; +pub use jsonrpsee_core::server::rpc_module::{ + DisconnectError, PendingSubscriptionSink, RpcModule, SendTimeoutError, SubscriptionMessage, SubscriptionSink, + TrySendError, +}; pub use jsonrpsee_core::{id_providers::*, traits::IdProvider}; pub use jsonrpsee_types as types; pub use server::{Builder as ServerBuilder, Server}; diff --git a/server/src/tests/ws.rs b/server/src/tests/ws.rs index 89b568590a..d7c245a3f8 100644 --- a/server/src/tests/ws.rs +++ b/server/src/tests/ws.rs @@ -27,7 +27,7 @@ use crate::tests::helpers::{deser_call, init_logger, server_with_context}; use crate::types::SubscriptionId; use crate::{RpcModule, ServerBuilder}; -use jsonrpsee_core::server::rpc_module::SendTimeoutError; +use jsonrpsee_core::server::rpc_module::{SendTimeoutError, SubscriptionMessage}; use jsonrpsee_core::{traits::IdProvider, Error}; use jsonrpsee_test_utils::helpers::*; use jsonrpsee_test_utils::mocks::{Id, WebSocketTestClient, WebSocketTestError}; @@ -672,8 +672,8 @@ async fn ws_server_backpressure_works() { "unsubscribe_with_backpressure_aggregation", move |_, pending, mut backpressure_tx| async move { let sink = pending.accept().await?; - let n = sink.build_message(&1).unwrap(); - let bp = sink.build_message(&2).unwrap(); + let n = SubscriptionMessage::from_json(&1).unwrap(); + let bp = SubscriptionMessage::from_json(&2).unwrap(); let mut msg = n.clone(); diff --git a/tests/tests/helpers.rs b/tests/tests/helpers.rs index fba54f443d..380c92d14e 100644 --- a/tests/tests/helpers.rs +++ b/tests/tests/helpers.rs @@ -31,7 +31,7 @@ use std::time::Duration; use futures::{SinkExt, Stream, StreamExt}; use jsonrpsee::core::server::host_filtering::AllowHosts; -use jsonrpsee::core::server::rpc_module::TrySendError; +use jsonrpsee::core::server::rpc_module::{SubscriptionMessage, TrySendError}; use jsonrpsee::core::{Error, SubscriptionResult}; use jsonrpsee::server::middleware::proxy_get_request::ProxyGetRequestLayer; use jsonrpsee::server::{ServerBuilder, ServerHandle}; @@ -232,7 +232,7 @@ pub async fn pipe_from_stream_and_drop( Some(item) => item, None => break, }; - let msg = match sink.build_message(&item) { + let msg = match SubscriptionMessage::from_json(&item) { Ok(msg) => msg, Err(e) => { sink.close(ErrorObject::owned(1, e.to_string(), None::<()>)).await; diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 1dd2d08f0b..66328a0121 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -40,6 +40,7 @@ use helpers::{ use hyper::http::HeaderValue; use jsonrpsee::core::client::{ClientT, IdKind, Subscription, SubscriptionClientT}; use jsonrpsee::core::params::{ArrayParams, BatchRequestBuilder}; +use jsonrpsee::core::server::rpc_module::SubscriptionMessage; use jsonrpsee::core::{Error, JsonValue}; use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::rpc_params; @@ -437,7 +438,7 @@ async fn ws_server_should_stop_subscription_after_client_drop() { "unsubscribe_hello", |_, pending, mut tx| async move { let sink = pending.accept().await.unwrap(); - let msg = sink.build_message(&1).unwrap(); + let msg = SubscriptionMessage::from_json(&1).unwrap(); sink.send(msg).await.unwrap(); sink.closed().await; let send_back = Arc::make_mut(&mut tx); diff --git a/tests/tests/proc_macros.rs b/tests/tests/proc_macros.rs index 3453286cfa..4125a3d648 100644 --- a/tests/tests/proc_macros.rs +++ b/tests/tests/proc_macros.rs @@ -42,6 +42,7 @@ use jsonrpsee::ws_client::*; use serde_json::json; mod rpc_impl { + use jsonrpsee::core::server::rpc_module::SubscriptionMessage; use jsonrpsee::core::{async_trait, RpcResult, SubscriptionResult}; use jsonrpsee::proc_macros::rpc; use jsonrpsee::PendingSubscriptionSink; @@ -170,15 +171,15 @@ mod rpc_impl { async fn sub(&self, pending: PendingSubscriptionSink) -> SubscriptionResult { let sink = pending.accept().await.unwrap(); - let _ = sink.send(sink.build_message(&"Response_A").unwrap()).await; - let _ = sink.send(sink.build_message(&"Response_B").unwrap()).await; + let _ = sink.send(SubscriptionMessage::from_json(&"Response_A").unwrap()).await; + let _ = sink.send(SubscriptionMessage::from_json(&"Response_B").unwrap()).await; Ok(()) } async fn sub_with_params(&self, pending: PendingSubscriptionSink, val: u32) -> SubscriptionResult { let sink = pending.accept().await.unwrap(); - let msg = sink.build_message(&val).unwrap(); + let msg = SubscriptionMessage::from_json(&val).unwrap(); let _ = sink.send(msg.clone()).await; let _ = sink.send(msg).await; @@ -198,7 +199,7 @@ mod rpc_impl { impl OnlyGenericSubscriptionServer for RpcServerImpl { async fn sub(&self, pending: PendingSubscriptionSink, _: String) -> SubscriptionResult { let sink = pending.accept().await.unwrap(); - let msg = sink.build_message(&"hello").unwrap(); + let msg = SubscriptionMessage::from_json(&"hello").unwrap(); let _ = sink.send(msg).await.unwrap(); Ok(()) diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index 0ec61efc04..34f6bdc641 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -242,7 +242,7 @@ async fn subscribing_without_server() { while let Some(letter) = stream_data.pop() { tracing::debug!("This is your friendly subscription sending data."); - let msg = sink.build_message(&letter).unwrap(); + let msg = SubscriptionMessage::from_json(&letter).unwrap(); let _ = sink.send(msg).await.unwrap(); tokio::time::sleep(std::time::Duration::from_millis(500)).await; } @@ -272,7 +272,7 @@ async fn close_test_subscribing_without_server() { module .register_subscription("my_sub", "my_sub", "my_unsub", |_, pending, _| async move { let sink = pending.accept().await.unwrap(); - let msg = sink.build_message(&"lo").unwrap(); + let msg = SubscriptionMessage::from_json(&"lo").unwrap(); // make sure to only send one item sink.send(msg.clone()).await.unwrap(); @@ -329,7 +329,7 @@ async fn subscribing_without_server_bad_params() { }; let sink = pending.accept().await.unwrap(); - let msg = sink.build_message(&p).unwrap(); + let msg = SubscriptionMessage::from_json(&p).unwrap(); sink.send(msg).await.unwrap(); Ok(()) @@ -439,7 +439,7 @@ async fn bounded_subscription_work() { let mut buf = VecDeque::new(); while let Some(n) = stream.next().await { - let msg = sink.build_message(&n).expect("usize infallible; qed"); + let msg = SubscriptionMessage::from_json(&n).expect("usize infallible; qed"); match sink.try_send(msg) { Err(TrySendError::Closed(_)) => panic!("This is a bug"), From 332d1821ee22273a859ddf226636d88fafd36a74 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 7 Feb 2023 18:55:04 +0100 Subject: [PATCH 47/60] remove resource limiting leftover --- proc-macros/src/attributes.rs | 15 +------- proc-macros/src/render_server.rs | 32 +---------------- proc-macros/src/rpc_macro.rs | 36 ++++--------------- .../method/method_unexpected_field.stderr | 2 +- .../sub/sub_unsupported_field.stderr | 2 +- 5 files changed, 10 insertions(+), 77 deletions(-) diff --git a/proc-macros/src/attributes.rs b/proc-macros/src/attributes.rs index 139f25e7d4..30c0797bd2 100644 --- a/proc-macros/src/attributes.rs +++ b/proc-macros/src/attributes.rs @@ -29,7 +29,7 @@ use std::{fmt, iter}; use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree}; use syn::parse::{Parse, ParseStream, Parser}; use syn::punctuated::Punctuated; -use syn::{spanned::Spanned, Attribute, Error, LitInt, LitStr, Token}; +use syn::{spanned::Spanned, Attribute, Error, LitStr, Token}; pub(crate) struct AttributeMeta { pub path: syn::Path, @@ -47,13 +47,6 @@ pub enum ParamKind { Map, } -#[derive(Debug, Clone)] -pub struct Resource { - pub name: LitStr, - pub assign: Token![=], - pub value: LitInt, -} - pub struct NameMapping { pub name: String, pub mapped: Option, @@ -93,12 +86,6 @@ impl Parse for Argument { } } -impl Parse for Resource { - fn parse(input: ParseStream) -> syn::Result { - Ok(Resource { name: input.parse()?, assign: input.parse()?, value: input.parse()? }) - } -} - impl Parse for NameMapping { fn parse(input: ParseStream) -> syn::Result { let name = input.parse::()?.value(); diff --git a/proc-macros/src/render_server.rs b/proc-macros/src/render_server.rs index f312c76f21..d8a1a69349 100644 --- a/proc-macros/src/render_server.rs +++ b/proc-macros/src/render_server.rs @@ -28,12 +28,11 @@ use std::collections::HashSet; use std::str::FromStr; use super::RpcDescription; -use crate::attributes::Resource; use crate::helpers::{generate_where_clause, is_option}; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, quote_spanned}; use syn::punctuated::Punctuated; -use syn::{parse_quote, token, AttrStyle, Attribute, Path, PathSegment, ReturnType, Token}; +use syn::{parse_quote, token, AttrStyle, Attribute, Path, PathSegment, ReturnType}; impl RpcDescription { pub(super) fn render_server(&self) -> Result { @@ -121,28 +120,6 @@ impl RpcDescription { }} } - /// Helper that will parse the resources passed to the macro and call the appropriate resource - /// builder to register the resource limits. - fn handle_resource_limits(resources: &Punctuated) -> TokenStream2 { - // Nothing to be done if no resources were set. - if resources.is_empty() { - return quote! {}; - } - - // Transform each resource into a call to `.resource(name, value)`. - let resources = resources.iter().map(|resource| { - let Resource { name, value, .. } = resource; - quote! { .resource(#name, #value)? } - }); - - quote! { - .and_then(|resource_builder| { - resource_builder #(#resources)*; - Ok(()) - }) - } - } - let methods = self .methods .iter() @@ -159,15 +136,12 @@ impl RpcDescription { check_name(&rpc_method_name, rust_method_name.span()); - let resources = handle_resource_limits(&method.resources); - if method.signature.sig.asyncness.is_some() { handle_register_result(quote! { rpc.register_async_method(#rpc_method_name, |params, context| async move { #parsing context.as_ref().#rust_method_name(#params_seq).await }) - #resources }) } else { let register_kind = @@ -178,7 +152,6 @@ impl RpcDescription { #parsing context.#rust_method_name(#params_seq) }) - #resources }) } }) @@ -213,14 +186,11 @@ impl RpcDescription { None => rpc_sub_name.clone(), }; - let resources = handle_resource_limits(&sub.resources); - handle_register_result(quote! { rpc.register_subscription(#rpc_sub_name, #rpc_notif_name, #rpc_unsub_name, |params, mut subscription_sink, context| async move { #parsing context.as_ref().#rust_method_name(subscription_sink, #params_seq).await }) - #resources }) }) .collect::>(); diff --git a/proc-macros/src/rpc_macro.rs b/proc-macros/src/rpc_macro.rs index 26e547c439..7003727268 100644 --- a/proc-macros/src/rpc_macro.rs +++ b/proc-macros/src/rpc_macro.rs @@ -29,7 +29,7 @@ use std::borrow::Cow; use crate::attributes::{ - optional, parse_param_kind, Aliases, Argument, AttributeMeta, MissingArgument, NameMapping, ParamKind, Resource, + optional, parse_param_kind, Aliases, Argument, AttributeMeta, MissingArgument, NameMapping, ParamKind, }; use crate::helpers::extract_doc_comments; use proc_macro2::TokenStream as TokenStream2; @@ -48,19 +48,17 @@ pub struct RpcMethod { pub returns: Option, pub signature: syn::TraitItemMethod, pub aliases: Vec, - pub resources: Punctuated, } impl RpcMethod { pub fn from_item(attr: Attribute, mut method: syn::TraitItemMethod) -> syn::Result { - let [aliases, blocking, name, param_kind, resources] = - AttributeMeta::parse(attr)?.retain(["aliases", "blocking", "name", "param_kind", "resources"])?; + let [aliases, blocking, name, param_kind] = + AttributeMeta::parse(attr)?.retain(["aliases", "blocking", "name", "param_kind"])?; let aliases = parse_aliases(aliases)?; let blocking = optional(blocking, Argument::flag)?.is_some(); let name = name?.string()?; let param_kind = parse_param_kind(param_kind)?; - let resources = optional(resources, Argument::group)?.unwrap_or_default(); let sig = method.sig.clone(); let docs = extract_doc_comments(&method.attrs); @@ -100,18 +98,7 @@ impl RpcMethod { // We've analyzed attributes and don't need them anymore. method.attrs.clear(); - Ok(Self { - aliases, - blocking, - name, - params, - param_kind, - returns, - signature: method, - docs, - resources, - deprecated, - }) + Ok(Self { aliases, blocking, name, params, param_kind, returns, signature: method, docs, deprecated }) } } @@ -133,21 +120,12 @@ pub struct RpcSubscription { pub signature: syn::TraitItemMethod, pub aliases: Vec, pub unsubscribe_aliases: Vec, - pub resources: Punctuated, } impl RpcSubscription { pub fn from_item(attr: syn::Attribute, mut sub: syn::TraitItemMethod) -> syn::Result { - let [aliases, item, name, param_kind, unsubscribe, unsubscribe_aliases, resources] = - AttributeMeta::parse(attr)?.retain([ - "aliases", - "item", - "name", - "param_kind", - "unsubscribe", - "unsubscribe_aliases", - "resources", - ])?; + let [aliases, item, name, param_kind, unsubscribe, unsubscribe_aliases] = AttributeMeta::parse(attr)? + .retain(["aliases", "item", "name", "param_kind", "unsubscribe", "unsubscribe_aliases"])?; let aliases = parse_aliases(aliases)?; let map = name?.value::()?; @@ -156,7 +134,6 @@ impl RpcSubscription { let item = item?.value()?; let param_kind = parse_param_kind(param_kind)?; let unsubscribe_aliases = parse_aliases(unsubscribe_aliases)?; - let resources = optional(resources, Argument::group)?.unwrap_or_default(); let sig = sub.sig.clone(); let docs = extract_doc_comments(&sub.attrs); @@ -193,7 +170,6 @@ impl RpcSubscription { signature: sub, aliases, docs, - resources, }) } } diff --git a/proc-macros/tests/ui/incorrect/method/method_unexpected_field.stderr b/proc-macros/tests/ui/incorrect/method/method_unexpected_field.stderr index 57c82ce5eb..8fecc8437a 100644 --- a/proc-macros/tests/ui/incorrect/method/method_unexpected_field.stderr +++ b/proc-macros/tests/ui/incorrect/method/method_unexpected_field.stderr @@ -1,4 +1,4 @@ -error: Unknown argument `magic`, expected one of: `aliases`, `blocking`, `name`, `param_kind`, `resources` +error: Unknown argument `magic`, expected one of: `aliases`, `blocking`, `name`, `param_kind` --> $DIR/method_unexpected_field.rs:6:25 | 6 | #[method(name = "foo", magic = false)] diff --git a/proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.stderr b/proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.stderr index 3eea012b51..70ad7bf7d0 100644 --- a/proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.stderr +++ b/proc-macros/tests/ui/incorrect/sub/sub_unsupported_field.stderr @@ -1,4 +1,4 @@ -error: Unknown argument `magic`, expected one of: `aliases`, `item`, `name`, `param_kind`, `unsubscribe`, `unsubscribe_aliases`, `resources` +error: Unknown argument `magic`, expected one of: `aliases`, `item`, `name`, `param_kind`, `unsubscribe`, `unsubscribe_aliases` --> tests/ui/incorrect/sub/sub_unsupported_field.rs:6:65 | 6 | #[subscription(name = "sub", unsubscribe = "unsub", item = u8, magic = true)] From e2c07e9129884a9610a22986084cb1f314f9b478 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 8 Feb 2023 08:37:11 +0100 Subject: [PATCH 48/60] Update core/src/server/rpc_module.rs --- core/src/server/rpc_module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 88c08c6d3b..70ed22f109 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -709,7 +709,7 @@ impl RpcModule { /// * `unsubscription_method` - name of the method to call to terminate a subscription /// * `callback` - A callback to invoke on each subscription; it takes three parameters: /// - [`Params`]: JSON-RPC parameters in the subscription call. - /// - [`PendingSubscriptionSink`]: A pending subscription waiting to accepted. + /// - [`PendingSubscriptionSink`]: A pending subscription waiting to be accepted, in order to send out messages on the subscription /// - Context: Any type that can be embedded into the [`RpcModule`]. /// /// # Examples From 935da72c312f0c678cfb71f9cbc704342104c688 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 8 Feb 2023 09:11:30 +0100 Subject: [PATCH 49/60] Update examples/examples/ws_pubsub_broadcast.rs --- examples/examples/ws_pubsub_broadcast.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/examples/ws_pubsub_broadcast.rs b/examples/examples/ws_pubsub_broadcast.rs index 6e548dcdb3..c4d00ef9d1 100644 --- a/examples/examples/ws_pubsub_broadcast.rs +++ b/examples/examples/ws_pubsub_broadcast.rs @@ -113,7 +113,7 @@ async fn pipe_from_stream_with_bounded_buffer( let notif = SubscriptionMessage::from_json(&item)?; // NOTE: this will block until there a spot in the queue - // and you might want to something smarter if it's + // and you might want to do something smarter if it's // critical that newer must be sent when they are produced. if sink.send(notif).await.is_err() { break; From ade35a7aa1a976ae5100974cd46bd4633016010e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 8 Feb 2023 09:14:18 +0100 Subject: [PATCH 50/60] Update examples/examples/ws_pubsub_broadcast.rs --- examples/examples/ws_pubsub_broadcast.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/examples/ws_pubsub_broadcast.rs b/examples/examples/ws_pubsub_broadcast.rs index c4d00ef9d1..78b9b20a81 100644 --- a/examples/examples/ws_pubsub_broadcast.rs +++ b/examples/examples/ws_pubsub_broadcast.rs @@ -114,7 +114,7 @@ async fn pipe_from_stream_with_bounded_buffer( // NOTE: this will block until there a spot in the queue // and you might want to do something smarter if it's - // critical that newer must be sent when they are produced. + // critical that "the most recent item" must be sent when it is produced. if sink.send(notif).await.is_err() { break; } From bb89d001d1f6561c925bec56af48a5ba48088d17 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 8 Feb 2023 09:25:26 +0100 Subject: [PATCH 51/60] revert unintentional change --- examples/examples/ws.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/examples/ws.rs b/examples/examples/ws.rs index 40079655de..15a2c8a0cb 100644 --- a/examples/examples/ws.rs +++ b/examples/examples/ws.rs @@ -50,9 +50,9 @@ async fn main() -> anyhow::Result<()> { } async fn run_server() -> anyhow::Result { - let server = ServerBuilder::default().set_backpressure_buffer_capacity(2).build("127.0.0.1:9944").await?; + let server = ServerBuilder::default().build("127.0.0.1:0").await?; let mut module = RpcModule::new(()); - module.register_method("say_hello", |_, _| Ok("a".repeat(1024)))?; + module.register_method("say_hello", |_, _| Ok("lo"))?; let addr = server.local_addr()?; let handle = server.start(module)?; From 5aae65108f35d5d54e517ebc06911116aea7463a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 8 Feb 2023 09:28:09 +0100 Subject: [PATCH 52/60] Update examples/examples/ws_pubsub_with_params.rs --- examples/examples/ws_pubsub_with_params.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index a33e772f45..6b7568b58e 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -65,7 +65,7 @@ async fn main() -> anyhow::Result<()> { async fn run_server() -> anyhow::Result { const LETTERS: &str = "abcdefghijklmnopqrstuvxyz"; - let server = ServerBuilder::default().set_backpressure_buffer_capacity(10).build("127.0.0.1:9944").await?; + let server = ServerBuilder::default().set_backpressure_buffer_capacity(10).build("127.0.0.1:0").await?; let mut module = RpcModule::new(()); module .register_subscription("sub_one_param", "sub_one_param", "unsub_one_param", |params, pending, _| async move { From 9b0d2ef6b84b3fa2dde5bc8d6c7573dfac6701f0 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 8 Feb 2023 09:41:10 +0100 Subject: [PATCH 53/60] fix more nits --- tests/tests/rpc_module.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index 34f6bdc641..f37d593920 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -276,10 +276,7 @@ async fn close_test_subscribing_without_server() { // make sure to only send one item sink.send(msg.clone()).await.unwrap(); - while !sink.is_closed() { - tracing::debug!("[test] Sink is open, sleeping"); - tokio::time::sleep(std::time::Duration::from_millis(500)).await; - } + sink.closed().await; match sink.send(msg).await { Ok(_) => panic!("The sink should be closed"), @@ -420,7 +417,7 @@ async fn reject_works() { } #[tokio::test] -async fn bounded_subscription_work() { +async fn bounded_subscription_works() { init_logger(); let (tx, mut rx) = mpsc::unbounded_channel::(); From d25062bda935402be753ac95fb9bd4c9c9456ce9 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 8 Feb 2023 12:03:05 +0100 Subject: [PATCH 54/60] improve SubscriptionEmptyErr --- core/src/error.rs | 62 +++++++++++++++++++++----------- core/src/lib.rs | 4 +-- core/src/server/rpc_module.rs | 49 ++++++++++++++++++++++--- proc-macros/src/render_server.rs | 2 +- tests/tests/rpc_module.rs | 4 +-- 5 files changed, 90 insertions(+), 31 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index a72abff562..04feb1e44f 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -188,46 +188,66 @@ impl From for Error { /// The error returned by the subscription's method for the rpc server implementation. /// -/// It contains no data, and neither is the error utilized. It provides an abstraction to make the -/// API more ergonomic while handling errors that may occur during the subscription callback. -#[derive(Debug, Clone, Copy)] -pub struct SubscriptionEmptyError; +/// It provides an abstraction to make the API more ergonomic while handling errors +/// that may occur during the subscription callback. +#[derive(Debug)] +pub enum SubscriptionCallbackError { + /// Error cause is propagated by other code or connection related. + None, + /// Some error happened to be logged. + Some(String), +} + +// User defined error. +impl From for SubscriptionCallbackError { + fn from(e: anyhow::Error) -> Self { + Self::Some(format!("Other: {}", e.to_string())) + } +} -impl From for SubscriptionEmptyError { - fn from(_: anyhow::Error) -> Self { - SubscriptionEmptyError +// User defined error. +impl From> for SubscriptionCallbackError { + fn from(e: Box) -> Self { + Self::Some(format!("Other: {}", e.to_string())) } } -impl From for SubscriptionEmptyError { - fn from(_: CallError) -> Self { - SubscriptionEmptyError +impl From for SubscriptionCallbackError { + fn from(e: CallError) -> Self { + Self::Some(e.to_string()) } } -impl From for SubscriptionEmptyError { +impl From for SubscriptionCallbackError { fn from(_: SubscriptionAcceptRejectError) -> Self { - SubscriptionEmptyError + Self::None } } -impl From for SubscriptionEmptyError { - fn from(_: serde_json::Error) -> Self { - SubscriptionEmptyError +impl From for SubscriptionCallbackError { + fn from(e: serde_json::Error) -> Self { + Self::Some(format!("Failed to parse SubscriptionMessage::from_json: {}", e.to_string())) + } +} + +#[cfg(feature = "server")] +impl From for SubscriptionCallbackError { + fn from(e: crate::server::rpc_module::TrySendError) -> Self { + Self::Some(format!("SubscriptionSink::try_send failed: {}", e.to_string())) } } #[cfg(feature = "server")] -impl From for SubscriptionEmptyError { - fn from(_: crate::server::rpc_module::TrySendError) -> Self { - SubscriptionEmptyError +impl From for SubscriptionCallbackError { + fn from(e: crate::server::rpc_module::DisconnectError) -> Self { + Self::Some(format!("SubscriptionSink::send failed: {}", e.to_string())) } } #[cfg(feature = "server")] -impl From for SubscriptionEmptyError { - fn from(_: crate::server::rpc_module::DisconnectError) -> Self { - SubscriptionEmptyError +impl From for SubscriptionCallbackError { + fn from(e: crate::server::rpc_module::SendTimeoutError) -> Self { + Self::Some(format!("SubscriptionSink::send_timeout failed: {}", e.to_string())) } } diff --git a/core/src/lib.rs b/core/src/lib.rs index 8649782aaa..96919676f7 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -58,7 +58,7 @@ cfg_client! { /// Shared tracing helpers to trace RPC calls. pub mod tracing; pub use async_trait::async_trait; -pub use error::{Error, SubscriptionAcceptRejectError, SubscriptionEmptyError}; +pub use error::{Error, SubscriptionAcceptRejectError, SubscriptionCallbackError}; /// JSON-RPC result. pub type RpcResult = std::result::Result; @@ -66,7 +66,7 @@ pub type RpcResult = std::result::Result; /// The return type of the subscription's method for the rpc server implementation. /// /// **Note**: The error does not contain any data and is discarded on drop. -pub type SubscriptionResult = Result<(), SubscriptionEmptyError>; +pub type SubscriptionResult = Result<(), SubscriptionCallbackError>; /// Empty server `RpcParams` type to use while registering modules. pub type EmptyServerParams = Vec<()>; diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 70ed22f109..99b44ffa6e 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -35,7 +35,7 @@ use crate::error::{Error, SubscriptionAcceptRejectError}; use crate::id_providers::RandomIntegerIdProvider; use crate::server::helpers::MethodSink; use crate::traits::{IdProvider, ToRpcParams}; -use crate::SubscriptionResult; +use crate::{SubscriptionCallbackError, SubscriptionResult}; use futures_util::future::Either; use futures_util::{future::BoxFuture, FutureExt}; use jsonrpsee_types::error::{CallError, ErrorCode, ErrorObject, ErrorObjectOwned}; @@ -80,6 +80,16 @@ pub enum TrySendError { Full(SubscriptionMessage), } +impl std::fmt::Display for TrySendError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let msg = match self { + Self::Closed(_) => "closed", + Self::Full(_) => "full", + }; + f.write_str(msg) + } +} + #[derive(Debug, Clone)] /// Represents whether a subscription was answered or not. pub enum SubscriptionAnswered { @@ -96,6 +106,12 @@ pub enum SubscriptionAnswered { #[derive(Debug)] pub struct DisconnectError(pub SubscriptionMessage); +impl std::fmt::Display for DisconnectError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("closed") + } +} + /// Error that may occur during `SubscriptionSink::send_timeout`. #[derive(Debug)] pub enum SendTimeoutError { @@ -106,6 +122,16 @@ pub enum SendTimeoutError { Closed(SubscriptionMessage), } +impl std::fmt::Display for SendTimeoutError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let msg = match self { + Self::Timeout(_) => "timed out waiting on send operation", + Self::Closed(_) => "closed", + }; + f.write_str(msg) + } +} + /// Helper struct to manage subscriptions. pub struct ConnState<'a> { /// Connection ID @@ -695,7 +721,7 @@ impl RpcModule { /// /// Furthermore, it generates the `unsubscribe implementation` where a `bool` is used as /// the result to indicate whether the subscription was successfully unsubscribed to or not. - /// For instance an `unsubscribe call` may fail if a non-existent subscriptionID is used in the call. + /// For instance an `unsubscribe call` may fail if a non-existent subscription ID is used in the call. /// /// This method ensures that the `subscription_method_name` and `unsubscription_method_name` are unique. /// The `notif_method_name` argument sets the content of the `method` field in the JSON document that @@ -711,6 +737,16 @@ impl RpcModule { /// - [`Params`]: JSON-RPC parameters in the subscription call. /// - [`PendingSubscriptionSink`]: A pending subscription waiting to be accepted, in order to send out messages on the subscription /// - Context: Any type that can be embedded into the [`RpcModule`]. + /// + /// # Returns + /// + /// An async block which returns `Result<(), SubscriptionCallbackError>` the error is simply + /// for a more ergonomic API and is not used (except logged for user-related caused errors). + /// By default jsonrpsee doesn't send any special close notification, + /// it can be a footgun if one wants to send out a "special notification" to indicate that an error occurred. + /// + /// If you want to a special error notification use `SubscriptionSink::close` or + /// `SubscriptionSink::send` before returning from the async block. /// /// # Examples /// @@ -727,6 +763,9 @@ impl RpcModule { /// let sink = pending.accept().await?; /// /// let sum = x + (*ctx); + /// + /// // NOTE: the error handling here is for easy of use + /// // and are thrown away /// let msg = SubscriptionMessage::from_json(&sum)?; /// sink.send(msg).await?; /// @@ -779,7 +818,7 @@ impl RpcModule { let result = subscribers.lock().remove(&key).is_some(); if !result { - tracing::warn!( + tracing::debug!( "Unsubscribe call `{}` subscription key={:?} not an active subscription", unsubscribe_method_name, key, @@ -817,8 +856,8 @@ impl RpcModule { let sub_fut = callback(params.into_owned(), sink, ctx.clone()); tokio::spawn(async move { - if sub_fut.await.is_err() { - tracing::warn!("Subscribe call `{subscribe_method_name}` closed"); + if let Err(SubscriptionCallbackError::Some(msg)) = sub_fut.await { + tracing::warn!("Subscribe call `{subscribe_method_name}` failed: {msg}"); } }); diff --git a/proc-macros/src/render_server.rs b/proc-macros/src/render_server.rs index d8a1a69349..49f13326c9 100644 --- a/proc-macros/src/render_server.rs +++ b/proc-macros/src/render_server.rs @@ -290,7 +290,7 @@ impl RpcDescription { let params_fields = quote! { #(#params_fields_seq),* }; let tracing = self.jrps_server_item(quote! { tracing }); let err = self.jrps_server_item(quote! { core::Error }); - let sub_err = self.jrps_server_item(quote! { core::SubscriptionEmptyError }); + let sub_err = self.jrps_server_item(quote! { core::SubscriptionCallbackError::None }); let tokio = self.jrps_server_item(quote! { tokio }); // Code to decode sequence of parameters from a JSON array. diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index f37d593920..b0735c9410 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -31,7 +31,7 @@ use std::time::Duration; use futures::StreamExt; use helpers::{init_logger, pipe_from_stream_and_drop}; -use jsonrpsee::core::error::{Error, SubscriptionEmptyError}; +use jsonrpsee::core::error::{Error, SubscriptionCallbackError}; use jsonrpsee::core::server::rpc_module::*; use jsonrpsee::core::EmptyServerParams; use jsonrpsee::types::error::{CallError, ErrorCode, ErrorObject, PARSE_ERROR_CODE}; @@ -321,7 +321,7 @@ async fn subscribing_without_server_bad_params() { Err(e) => { let err: ErrorObjectOwned = e.into(); let _ = pending.reject(err).await; - return Err(SubscriptionEmptyError.into()); + return Err(SubscriptionCallbackError::None); } }; From d3cbf69061654c87f7a30e6f065471d6dd2786f5 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 8 Feb 2023 13:20:15 +0100 Subject: [PATCH 55/60] clippy --fix --- core/src/error.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index 04feb1e44f..9ffd7360be 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -201,14 +201,14 @@ pub enum SubscriptionCallbackError { // User defined error. impl From for SubscriptionCallbackError { fn from(e: anyhow::Error) -> Self { - Self::Some(format!("Other: {}", e.to_string())) + Self::Some(format!("Other: {e}")) } } // User defined error. impl From> for SubscriptionCallbackError { fn from(e: Box) -> Self { - Self::Some(format!("Other: {}", e.to_string())) + Self::Some(format!("Other: {e}")) } } @@ -226,28 +226,28 @@ impl From for SubscriptionCallbackError { impl From for SubscriptionCallbackError { fn from(e: serde_json::Error) -> Self { - Self::Some(format!("Failed to parse SubscriptionMessage::from_json: {}", e.to_string())) + Self::Some(format!("Failed to parse SubscriptionMessage::from_json: {e}")) } } #[cfg(feature = "server")] impl From for SubscriptionCallbackError { fn from(e: crate::server::rpc_module::TrySendError) -> Self { - Self::Some(format!("SubscriptionSink::try_send failed: {}", e.to_string())) + Self::Some(format!("SubscriptionSink::try_send failed: {e}")) } } #[cfg(feature = "server")] impl From for SubscriptionCallbackError { fn from(e: crate::server::rpc_module::DisconnectError) -> Self { - Self::Some(format!("SubscriptionSink::send failed: {}", e.to_string())) + Self::Some(format!("SubscriptionSink::send failed: {e}")) } } #[cfg(feature = "server")] impl From for SubscriptionCallbackError { fn from(e: crate::server::rpc_module::SendTimeoutError) -> Self { - Self::Some(format!("SubscriptionSink::send_timeout failed: {}", e.to_string())) + Self::Some(format!("SubscriptionSink::send_timeout failed: {e}")) } } From 1f1148ba4801220e26cb26c625532f3ef6d35f2e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 8 Feb 2023 16:31:49 +0100 Subject: [PATCH 56/60] bring back subscription limit --- core/src/server/helpers.rs | 55 +++++++++++++++ core/src/server/rpc_module.rs | 16 ++++- server/src/server.rs | 16 +++++ server/src/transport/ws.rs | 41 ++++++++--- tests/tests/integration_tests.rs | 112 ++++++++++++++++++++++++++++++- 5 files changed, 227 insertions(+), 13 deletions(-) diff --git a/core/src/server/helpers.rs b/core/src/server/helpers.rs index 397020a3e1..92f166faa2 100644 --- a/core/src/server/helpers.rs +++ b/core/src/server/helpers.rs @@ -25,6 +25,7 @@ // DEALINGS IN THE SOFTWARE. use std::io; +use std::sync::Arc; use std::time::Duration; use crate::tracing::tx_log_from_str; @@ -33,6 +34,7 @@ use jsonrpsee_types::error::{ErrorCode, ErrorObject, ErrorResponse, OVERSIZED_RE use jsonrpsee_types::{Id, InvalidRequest, Response}; use serde::Serialize; use tokio::sync::mpsc::{self, Permit}; +use tokio::sync::{Notify, OwnedSemaphorePermit, Semaphore}; use super::rpc_module::{DisconnectError, SendTimeoutError, SubscriptionMessage, TrySendError}; @@ -189,6 +191,59 @@ pub fn prepare_error(data: &[u8]) -> (Id<'_>, ErrorCode) { } } +/// A permitted subscription. +#[derive(Debug)] +pub struct SubscriptionPermit { + _permit: OwnedSemaphorePermit, + resource: Arc, +} + +impl SubscriptionPermit { + /// Get the handle to [`tokio::sync::Notify`]. + pub fn handle(&self) -> Arc { + self.resource.clone() + } +} + +/// Wrapper over [`tokio::sync::Notify`] with bounds check. +#[derive(Debug, Clone)] +pub struct BoundedSubscriptions { + resource: Arc, + guard: Arc, + max: u32, +} + +impl BoundedSubscriptions { + /// Create a new bounded subscription. + pub fn new(max_subscriptions: u32) -> Self { + Self { + resource: Arc::new(Notify::new()), + guard: Arc::new(Semaphore::new(max_subscriptions as usize)), + max: max_subscriptions, + } + } + + /// Attempts to acquire a subscription slot. + /// + /// Fails if `max_subscriptions` have been exceeded. + pub fn acquire(&self) -> Option { + Arc::clone(&self.guard) + .try_acquire_owned() + .ok() + .map(|p| SubscriptionPermit { _permit: p, resource: self.resource.clone() }) + } + + /// Get the maximum number of permitted subscriptions. + pub const fn max(&self) -> u32 { + self.max + } + + /// Close all subscriptions. + pub fn close(&self) { + self.resource.notify_waiters(); + } +} + /// Represent the response to method call. #[derive(Debug, Clone)] pub struct MethodResponse { diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 99b44ffa6e..8fe96918fb 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -33,7 +33,7 @@ use std::time::Duration; use crate::error::{Error, SubscriptionAcceptRejectError}; use crate::id_providers::RandomIntegerIdProvider; -use crate::server::helpers::MethodSink; +use crate::server::helpers::{BoundedSubscriptions, MethodSink}; use crate::traits::{IdProvider, ToRpcParams}; use crate::{SubscriptionCallbackError, SubscriptionResult}; use futures_util::future::Either; @@ -48,7 +48,7 @@ use rustc_hash::FxHashMap; use serde::{de::DeserializeOwned, Serialize}; use tokio::sync::{mpsc, oneshot}; -use super::helpers::MethodResponse; +use super::helpers::{MethodResponse, SubscriptionPermit}; /// A `MethodCallback` is an RPC endpoint, callable with a standard JSON-RPC request, /// implemented as a function pointer to a `Fn` function taking four arguments: @@ -138,6 +138,8 @@ pub struct ConnState<'a> { pub conn_id: ConnectionId, /// ID provider. pub id_provider: &'a dyn IdProvider, + /// Subscription limit + pub subscription_permit: SubscriptionPermit, } /// Outcome of a successful terminated subscription. @@ -490,6 +492,8 @@ impl Methods { async fn inner_call(&self, req: Request<'_>, tx: mpsc::Sender) -> CallResponse { let id = req.id.clone(); let params = Params::new(req.params.map(|params| params.get())); + let bounded_subs = BoundedSubscriptions::new(u32::MAX); + let subscription_permit = bounded_subs.acquire().expect("u32::MAX permits is sufficient; qed"); let response = match self.method(&req.method).map(|c| &c.callback) { None => CallResponse::Call(MethodResponse::error(req.id, ErrorObject::from(ErrorCode::MethodNotFound))), @@ -498,7 +502,7 @@ impl Methods { CallResponse::Call((cb)(id.into_owned(), params.into_owned(), 0, usize::MAX).await) } Some(MethodKind::Subscription(cb)) => { - let conn_state = ConnState { conn_id: 0, id_provider: &RandomIntegerIdProvider }; + let conn_state = ConnState { conn_id: 0, id_provider: &RandomIntegerIdProvider, subscription_permit }; let res = (cb)(id, params, MethodSink::new(tx), conn_state).await; CallResponse::Subscribe(res) @@ -847,6 +851,7 @@ impl RpcModule { uniq_sub, id: id.clone().into_owned(), subscribe: tx, + permit: conn.subscription_permit, }; // The subscription callback is a future from the subscription @@ -945,6 +950,8 @@ pub struct PendingSubscriptionSink { id: Id<'static>, /// Sender to answer the subscribe call. subscribe: oneshot::Sender, + /// Subscription permit. + permit: SubscriptionPermit, } impl PendingSubscriptionSink { @@ -976,6 +983,7 @@ impl PendingSubscriptionSink { subscribers: self.subscribers, uniq_sub: self.uniq_sub, unsubscribe: IsUnsubscribed(tx), + _permit: Arc::new(self.permit), }) } else { Err(SubscriptionAcceptRejectError::MessageTooLarge) @@ -996,6 +1004,8 @@ pub struct SubscriptionSink { uniq_sub: SubscriptionKey, /// A future to that fires once the unsubscribe method has been called. unsubscribe: IsUnsubscribed, + /// Subscription permit + _permit: Arc, } impl SubscriptionSink { diff --git a/server/src/server.rs b/server/src/server.rs index b902c360d7..5d9fcfc932 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -120,6 +120,7 @@ where let max_request_body_size = self.cfg.max_request_body_size; let max_response_body_size = self.cfg.max_response_body_size; let max_log_length = self.cfg.max_log_length; + let max_subscriptions_per_connection = self.cfg.max_subscriptions_per_connection; let allow_hosts = self.cfg.allow_hosts; let logger = self.logger; let batch_requests_supported = self.cfg.batch_requests_supported; @@ -140,6 +141,7 @@ where max_request_body_size, max_response_body_size, max_log_length, + max_subscriptions_per_connection, batch_requests_supported, id_provider: id_provider.clone(), ping_interval: self.cfg.ping_interval, @@ -178,6 +180,8 @@ struct Settings { /// /// Logs bigger than this limit will be truncated. max_log_length: u32, + /// Maximum number of subscriptions per connection. + max_subscriptions_per_connection: u32, /// Host filtering. allow_hosts: AllowHosts, /// Whether batch requests are supported by this server or not. @@ -201,6 +205,7 @@ impl Default for Settings { max_response_body_size: TEN_MB_SIZE_BYTES, max_log_length: 4096, max_connections: MAX_CONNECTIONS, + max_subscriptions_per_connection: 1024, batch_requests_supported: true, allow_hosts: AllowHosts::Any, tokio_runtime: None, @@ -265,6 +270,12 @@ impl Builder { self } + /// Set the maximum number of connections allowed. Default is 1024. + pub fn max_subscriptions_per_connection(mut self, max: u32) -> Self { + self.settings.max_subscriptions_per_connection = max; + self + } + /// Add a logger to the builder [`Logger`](../jsonrpsee_core/logger/trait.Logger.html). /// /// ``` @@ -533,6 +544,8 @@ pub(crate) struct ServiceData { /// /// Logs bigger than this limit will be truncated. pub(crate) max_log_length: u32, + /// Maximum number of subscriptions per connection. + pub(crate) max_subscriptions_per_connection: u32, /// Whether batch requests are supported by this server or not. pub(crate) batch_requests_supported: bool, /// Subscription ID provider. @@ -721,6 +734,8 @@ struct ProcessConnection { /// /// Logs bigger than this limit will be truncated. max_log_length: u32, + /// Maximum number of subscriptions per connection. + max_subscriptions_per_connection: u32, /// Whether batch requests are supported by this server or not. batch_requests_supported: bool, /// Subscription ID provider. @@ -789,6 +804,7 @@ fn process_connection<'a, L: Logger, B, U>( max_request_body_size: cfg.max_request_body_size, max_response_body_size: cfg.max_response_body_size, max_log_length: cfg.max_log_length, + max_subscriptions_per_connection: cfg.max_subscriptions_per_connection, batch_requests_supported: cfg.batch_requests_supported, id_provider: cfg.id_provider, ping_interval: cfg.ping_interval, diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 18a439c746..199aa9acbe 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -11,13 +11,16 @@ use futures_util::io::{BufReader, BufWriter}; use futures_util::stream::FuturesOrdered; use futures_util::{Future, FutureExt, StreamExt}; use hyper::upgrade::Upgraded; -use jsonrpsee_core::server::helpers::{prepare_error, BatchResponse, BatchResponseBuilder, MethodResponse, MethodSink}; +use jsonrpsee_core::server::helpers::{ + prepare_error, BatchResponse, BatchResponseBuilder, BoundedSubscriptions, MethodResponse, MethodSink, +}; use jsonrpsee_core::server::rpc_module::{CallResponse, ConnState, MethodKind, Methods, SubscriptionAnswered}; use jsonrpsee_core::tracing::{rx_log_from_json, tx_log_from_str}; use jsonrpsee_core::traits::IdProvider; use jsonrpsee_core::{Error, JsonRawValue}; use jsonrpsee_types::error::{ - reject_too_big_request, ErrorCode, BATCHES_NOT_SUPPORTED_CODE, BATCHES_NOT_SUPPORTED_MSG, + reject_too_big_request, reject_too_many_subscriptions, ErrorCode, BATCHES_NOT_SUPPORTED_CODE, + BATCHES_NOT_SUPPORTED_MSG, }; use jsonrpsee_types::{ErrorObject, Id, InvalidRequest, Notification, Params, Request}; use soketto::connection::Error as SokettoError; @@ -54,6 +57,7 @@ pub(crate) struct Batch<'a, L: Logger> { #[derive(Debug, Clone)] pub(crate) struct CallData<'a, L: Logger> { pub(crate) conn_id: usize, + pub(crate) bounded_subscriptions: BoundedSubscriptions, pub(crate) id_provider: &'a dyn IdProvider, pub(crate) methods: &'a Methods, pub(crate) max_response_body_size: u32, @@ -167,8 +171,17 @@ pub(crate) async fn execute_call_with_tracing<'a, L: Logger>(req: Request<'a>, c /// Returns `(MethodResponse, None)` on every call that isn't a subscription /// Otherwise `(MethodResponse, Some(PendingSubscriptionCallTx)`. pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData<'_, L>) -> CallResponse { - let CallData { methods, max_response_body_size, max_log_length, conn_id, id_provider, sink, logger, request_start } = - call; + let CallData { + methods, + max_response_body_size, + max_log_length, + conn_id, + id_provider, + sink, + logger, + request_start, + bounded_subscriptions, + } = call; rx_log_from_json(&req, call.max_log_length); @@ -199,10 +212,16 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData MethodKind::Subscription(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::Subscription, TransportProtocol::WebSocket); - let conn_state = ConnState { conn_id, id_provider }; - let response = callback(id.clone(), params, sink.clone(), conn_state).await; - - CallResponse::Subscribe(response) + if let Some(p) = bounded_subscriptions.acquire() { + tracing::info!("{:?}", p); + let conn_state = ConnState { conn_id, id_provider, subscription_permit: p }; + let response = callback(id.clone(), params, sink.clone(), conn_state).await; + CallResponse::Subscribe(response) + } else { + let response = + MethodResponse::error(id, reject_too_many_subscriptions(bounded_subscriptions.max())); + CallResponse::Call(response) + } } MethodKind::Unsubscription(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::Unsubscription, TransportProtocol::WebSocket); @@ -231,6 +250,7 @@ pub(crate) async fn background_task( max_request_body_size, max_response_body_size, max_log_length, + max_subscriptions_per_connection, batch_requests_supported, stop_handle, id_provider, @@ -246,6 +266,7 @@ pub(crate) async fn background_task( let (tx, rx) = mpsc::channel::(backpressure_buffer_capacity as usize); let (conn_tx, conn_rx) = oneshot::channel(); let sink = MethodSink::new_with_limit(tx, max_response_body_size, max_log_length); + let bounded_subscriptions = BoundedSubscriptions::new(max_subscriptions_per_connection); // Spawn another task that sends out the responses on the Websocket. tokio::spawn(send_task(rx, sender, stop_handle.clone(), ping_interval, conn_rx)); @@ -329,10 +350,12 @@ pub(crate) async fn background_task( let sink = sink.clone(); let methods = &methods; let id_provider = &*id_provider; + let bounded_subscriptions = bounded_subscriptions.clone(); let fut = async move { let call = CallData { conn_id: conn_id as usize, + bounded_subscriptions, max_response_body_size, max_log_length, methods, @@ -374,12 +397,14 @@ pub(crate) async fn background_task( let sink = sink.clone(); let id_provider = id_provider.clone(); let data = std::mem::take(&mut data); + let bounded_subscriptions = bounded_subscriptions.clone(); let fut = async move { let response = process_batch_request(Batch { data, call: CallData { conn_id: conn_id as usize, + bounded_subscriptions, max_response_body_size, max_log_length, methods, diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index 66328a0121..4d4381d4ec 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -34,8 +34,8 @@ use std::time::Duration; use futures::{channel::mpsc, StreamExt, TryStreamExt}; use helpers::{ - init_logger, server, server_with_access_control, server_with_health_api, server_with_subscription, - server_with_subscription_and_handle, + init_logger, pipe_from_stream_and_drop, server, server_with_access_control, server_with_health_api, + server_with_subscription, server_with_subscription_and_handle, }; use hyper::http::HeaderValue; use jsonrpsee::core::client::{ClientT, IdKind, Subscription, SubscriptionClientT}; @@ -46,6 +46,8 @@ use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::rpc_params; use jsonrpsee::types::error::{ErrorObject, UNKNOWN_ERROR_CODE}; use jsonrpsee::ws_client::WsClientBuilder; +use tokio::time::interval; +use tokio_stream::wrappers::IntervalStream; use tower_http::cors::CorsLayer; #[tokio::test] @@ -724,6 +726,112 @@ async fn http_batch_works() { assert_eq!(err_responses, vec![&ErrorObject::borrowed(UNKNOWN_ERROR_CODE, &"Custom error: err", None)]); } +#[tokio::test] +async fn ws_server_limit_subs_per_conn_works() { + use futures::StreamExt; + use jsonrpsee::types::error::{CallError, TOO_MANY_SUBSCRIPTIONS_CODE, TOO_MANY_SUBSCRIPTIONS_MSG}; + use jsonrpsee::{server::ServerBuilder, RpcModule}; + + init_logger(); + + let server = ServerBuilder::default().max_subscriptions_per_connection(10).build("127.0.0.1:0").await.unwrap(); + let server_url = format!("ws://{}", server.local_addr().unwrap()); + + let mut module = RpcModule::new(()); + + module + .register_subscription("subscribe_forever", "n", "unsubscribe_forever", |_, pending, _| async move { + let interval = interval(Duration::from_millis(50)); + let stream = IntervalStream::new(interval).map(move |_| 0_usize); + + pipe_from_stream_and_drop(pending, stream).await + }) + .unwrap(); + let _handle = server.start(module).unwrap(); + + let c1 = WsClientBuilder::default().build(&server_url).await.unwrap(); + let c2 = WsClientBuilder::default().build(&server_url).await.unwrap(); + + let mut subs1 = Vec::new(); + let mut subs2 = Vec::new(); + + for _ in 0..10 { + subs1.push( + c1.subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever") + .await + .unwrap(), + ); + subs2.push( + c2.subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever") + .await + .unwrap(), + ); + } + + let err1 = c1.subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever").await; + let err2 = c1.subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever").await; + + let data = "\"Exceeded max limit of 10\""; + + assert!( + matches!(err1, Err(Error::Call(CallError::Custom(err))) if err.code() == TOO_MANY_SUBSCRIPTIONS_CODE && err.message() == TOO_MANY_SUBSCRIPTIONS_MSG && err.data().unwrap().get() == data) + ); + assert!( + matches!(err2, Err(Error::Call(CallError::Custom(err))) if err.code() == TOO_MANY_SUBSCRIPTIONS_CODE && err.message() == TOO_MANY_SUBSCRIPTIONS_MSG && err.data().unwrap().get() == data) + ); +} + +#[tokio::test] +async fn ws_server_unsub_methods_should_ignore_sub_limit() { + use futures::StreamExt; + use jsonrpsee::core::client::SubscriptionKind; + use jsonrpsee::{server::ServerBuilder, RpcModule}; + + init_logger(); + + let server = ServerBuilder::default().max_subscriptions_per_connection(10).build("127.0.0.1:0").await.unwrap(); + let server_url = format!("ws://{}", server.local_addr().unwrap()); + + let mut module = RpcModule::new(()); + + module + .register_subscription("subscribe_forever", "n", "unsubscribe_forever", |_, pending, _| async { + let interval = interval(Duration::from_millis(50)); + let stream = IntervalStream::new(interval).map(move |_| 0_usize); + + pipe_from_stream_and_drop(pending, stream).await + }) + .unwrap(); + let _handle = server.start(module).unwrap(); + + let client = WsClientBuilder::default().build(&server_url).await.unwrap(); + + // Add 10 subscriptions (this should fill our subscrition limit for this connection): + let mut subs = Vec::new(); + for _ in 0..10 { + subs.push( + client + .subscribe::("subscribe_forever", rpc_params![], "unsubscribe_forever") + .await + .unwrap(), + ); + } + + // Get the ID of one of them: + let last_sub = subs.pop().unwrap(); + let last_sub_id = match last_sub.kind() { + SubscriptionKind::Subscription(id) => id.clone(), + _ => panic!("Expected a subscription Id to be present"), + }; + + // Manually call the unsubscribe function for this subscription: + let res: Result = client.request("unsubscribe_forever", rpc_params![last_sub_id]).await; + + // This should not hit any limits, and unsubscription should have worked: + assert!(res.is_ok(), "Unsubscription method was successfully called"); + assert!(res.unwrap(), "Unsubscription was successful"); +} + #[tokio::test] async fn http_unsupported_methods_dont_work() { use hyper::{Body, Client, Method, Request}; From fce6d4514bb0fefd9c319412022bd0b6270f7bc2 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 8 Feb 2023 16:37:43 +0100 Subject: [PATCH 57/60] server: `set_message_buffer_capacity` --- examples/examples/ws_pubsub_broadcast.rs | 2 +- examples/examples/ws_pubsub_with_params.rs | 2 +- server/src/server.rs | 16 ++++++++-------- server/src/tests/ws.rs | 2 +- server/src/transport/ws.rs | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/examples/ws_pubsub_broadcast.rs b/examples/examples/ws_pubsub_broadcast.rs index 78b9b20a81..3ff2de6162 100644 --- a/examples/examples/ws_pubsub_broadcast.rs +++ b/examples/examples/ws_pubsub_broadcast.rs @@ -68,7 +68,7 @@ async fn main() -> anyhow::Result<()> { async fn run_server() -> anyhow::Result { // let's configure the server only hold 5 messages in memory. - let server = ServerBuilder::default().set_backpressure_buffer_capacity(5).build("127.0.0.1:0").await?; + let server = ServerBuilder::default().set_message_buffer_capacity(5).build("127.0.0.1:0").await?; let (tx, _rx) = broadcast::channel::(16); let mut module = RpcModule::new(tx.clone()); diff --git a/examples/examples/ws_pubsub_with_params.rs b/examples/examples/ws_pubsub_with_params.rs index 6b7568b58e..0b4d910489 100644 --- a/examples/examples/ws_pubsub_with_params.rs +++ b/examples/examples/ws_pubsub_with_params.rs @@ -65,7 +65,7 @@ async fn main() -> anyhow::Result<()> { async fn run_server() -> anyhow::Result { const LETTERS: &str = "abcdefghijklmnopqrstuvxyz"; - let server = ServerBuilder::default().set_backpressure_buffer_capacity(10).build("127.0.0.1:0").await?; + let server = ServerBuilder::default().set_message_buffer_capacity(10).build("127.0.0.1:0").await?; let mut module = RpcModule::new(()); module .register_subscription("sub_one_param", "sub_one_param", "unsub_one_param", |params, pending, _| async move { diff --git a/server/src/server.rs b/server/src/server.rs index 5d9fcfc932..4511a25cb5 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -151,7 +151,7 @@ where max_connections: self.cfg.max_connections, enable_http: self.cfg.enable_http, enable_ws: self.cfg.enable_ws, - backpressure_buffer_capacity: self.cfg.backpressure_buffer_capacity, + message_buffer_capacity: self.cfg.message_buffer_capacity, }; process_connection(&self.service_builder, &connection_guard, data, socket, &mut connections); id = id.wrapping_add(1); @@ -195,7 +195,7 @@ struct Settings { /// Enable WS. enable_ws: bool, /// Number of messages that server is allowed to `buffer` until backpressure kicks in. - backpressure_buffer_capacity: u32, + message_buffer_capacity: u32, } impl Default for Settings { @@ -212,7 +212,7 @@ impl Default for Settings { ping_interval: Duration::from_secs(60), enable_http: true, enable_ws: true, - backpressure_buffer_capacity: 1024, + message_buffer_capacity: 1024, } } } @@ -450,8 +450,8 @@ impl Builder { /// /// Panics if the buffer capacity is 0. /// - pub fn set_backpressure_buffer_capacity(mut self, c: u32) -> Self { - self.settings.backpressure_buffer_capacity = c; + pub fn set_message_buffer_capacity(mut self, c: u32) -> Self { + self.settings.message_buffer_capacity = c; self } @@ -565,7 +565,7 @@ pub(crate) struct ServiceData { /// Enable WS. pub(crate) enable_ws: bool, /// Number of messages that server is allowed `buffer` until backpressure kicks in. - pub(crate) backpressure_buffer_capacity: u32, + pub(crate) message_buffer_capacity: u32, } /// JsonRPSee service compatible with `tower`. @@ -755,7 +755,7 @@ struct ProcessConnection { /// Allow JSON-RPC WS request and WS upgrade requests. enable_ws: bool, /// Number of messages that server is allowed `buffer` until backpressure kicks in. - backpressure_buffer_capacity: u32, + message_buffer_capacity: u32, } #[instrument(name = "connection", skip_all, fields(remote_addr = %cfg.remote_addr, conn_id = %cfg.conn_id), level = "INFO")] @@ -814,7 +814,7 @@ fn process_connection<'a, L: Logger, B, U>( conn: Arc::new(conn), enable_http: cfg.enable_http, enable_ws: cfg.enable_ws, - backpressure_buffer_capacity: cfg.backpressure_buffer_capacity, + message_buffer_capacity: cfg.message_buffer_capacity, }, }; diff --git a/server/src/tests/ws.rs b/server/src/tests/ws.rs index d7c245a3f8..8d60174256 100644 --- a/server/src/tests/ws.rs +++ b/server/src/tests/ws.rs @@ -656,7 +656,7 @@ async fn ws_server_backpressure_works() { let (backpressure_tx, mut backpressure_rx) = tokio::sync::mpsc::channel::<()>(1); let server = ServerBuilder::default() - .set_backpressure_buffer_capacity(5) + .set_message_buffer_capacity(5) .build("127.0.0.1:0") .with_default_timeout() .await diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 199aa9acbe..225a490b11 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -258,12 +258,12 @@ pub(crate) async fn background_task( conn_id, logger, remote_addr, - backpressure_buffer_capacity, + message_buffer_capacity, conn, .. } = svc; - let (tx, rx) = mpsc::channel::(backpressure_buffer_capacity as usize); + let (tx, rx) = mpsc::channel::(message_buffer_capacity as usize); let (conn_tx, conn_rx) = oneshot::channel(); let sink = MethodSink::new_with_limit(tx, max_response_body_size, max_log_length); let bounded_subscriptions = BoundedSubscriptions::new(max_subscriptions_per_connection); From c141dbf5d57ba42362ca6842a1a0c3050f3036e1 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 8 Feb 2023 17:15:06 +0100 Subject: [PATCH 58/60] rpc module: revert raw_json_request API --- core/src/server/rpc_module.rs | 106 +++++++++++++++++----------------- server/src/tests/ws.rs | 1 - server/src/transport/ws.rs | 1 - tests/tests/proc_macros.rs | 27 +++------ tests/tests/rpc_module.rs | 9 ++- 5 files changed, 67 insertions(+), 77 deletions(-) diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 8fe96918fb..e74dabcd0c 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -71,6 +71,13 @@ pub type ConnectionId = usize; /// Max response size. pub type MaxResponseSize = usize; +/// Raw response from an RPC +/// A 3-tuple containing: +/// - Call result as a `String`, +/// - a [`mpsc::UnboundedReceiver`] to receive future subscription results +/// - a [`crate::server::helpers::SubscriptionPermit`] to allow subscribers to notify their [`SubscriptionSink`] when they disconnect. +pub type RawRpcResponse = (MethodResponse, mpsc::Receiver, SubscriptionPermit, mpsc::Sender); + /// Error that may occur during `SubscriptionSink::try_send`. #[derive(Debug)] pub enum TrySendError { @@ -423,9 +430,7 @@ impl Methods { let params = params.to_rpc_params()?; let req = Request::new(method.into(), params.as_ref().map(|p| p.as_ref()), Id::Number(0)); tracing::trace!("[Methods::call] Method: {:?}, params: {:?}", method, params); - let (tx, _rx) = mpsc::channel(1); - let call_resp = self.inner_call(req, tx).await; - let resp = call_resp.as_response(); + let (resp, _, _, _) = self.inner_call(req, 1).await; if resp.success { serde_json::from_str::>(&resp.result).map(|r| r.result).map_err(Into::into) @@ -439,12 +444,7 @@ impl Methods { /// Make a request (JSON-RPC method call or subscription) by using raw JSON. /// - /// This is a low-level API where you might be able build "in-memory" client - /// but it's not ergonomic to use, see [`Methods::call`] and [`Methods::subscribe_bounded`] - /// for more ergonomic APIs to perform method calls or subscriptions for testing or other purposes. - /// - /// After this method returns the responses can be read on the corresponding `Receiver` to the `Sender` - /// that was passed into to the call. + /// Returns the raw JSON response to the call and a stream to receive notifications if the call was a subscription. /// /// # Examples /// @@ -456,63 +456,68 @@ impl Methods { /// use futures_util::StreamExt; /// /// let mut module = RpcModule::new(()); - /// - /// module.register_subscription("hi", "hi", "goodbye", |_, pending, _| async move { - /// let sink = pending.accept().await?; - /// let msg = SubscriptionMessage::from_json(&"one answer").unwrap(); - /// sink.send(msg).await.unwrap(); - /// - /// Ok(()) + /// module.register_subscription("hi", "hi", "goodbye", |_, pending, _| async { + /// let sink = pending.accept().await?; + /// let msg = SubscriptionMessage::from_json(&"one answer")?; + /// sink.send(msg).await?; + /// Ok(()) /// }).unwrap(); - /// - /// let (tx, mut rx) = tokio::sync::mpsc::channel(16); - /// module.raw_json_request(r#"{"jsonrpc":"2.0","method":"hi","id":0}"#, tx).await.unwrap(); - /// let sub_resp = rx.recv().await.unwrap(); - /// // the subscription ID is sent in the `result` field. - /// let sub_resp = serde_json::from_str::>(&sub_resp).unwrap(); - /// - /// let sub_notif = rx.recv().await.unwrap(); + /// let (resp, mut stream) = module.raw_json_request(r#"{"jsonrpc":"2.0","method":"hi","id":0}"#, 1).await.unwrap(); + /// let resp = serde_json::from_str::>(&resp.result).unwrap(); + /// let sub_resp = stream.recv().await.unwrap(); /// assert_eq!( - /// format!(r#"{{"jsonrpc":"2.0","method":"hi","params":{{"subscription":{},"result":"one answer"}}}}"#, sub_resp.result), - /// sub_notif + /// format!(r#"{{"jsonrpc":"2.0","method":"hi","params":{{"subscription":{},"result":"one answer"}}}}"#, resp.result), + /// sub_resp /// ); /// } /// ``` - pub async fn raw_json_request(&self, request: &str, tx: mpsc::Sender) -> Result<(), Error> { + pub async fn raw_json_request( + &self, + request: &str, + buf_size: usize, + ) -> Result<(MethodResponse, mpsc::Receiver), Error> { tracing::trace!("[Methods::raw_json_request] Request: {:?}", request); let req: Request = serde_json::from_str(request)?; - if let CallResponse::Call(r) = self.inner_call(req, tx.clone()).await { - tx.send(r.result).await.map_err(|_| Error::Custom("Connection dropped; you have to keep the corresponding `Receiver` for `Methods::raw_json_request` to work".to_string()))?; - } + let (resp, rx, _, _) = self.inner_call(req, buf_size).await; - Ok(()) + Ok((resp, rx)) } /// Execute a callback. - async fn inner_call(&self, req: Request<'_>, tx: mpsc::Sender) -> CallResponse { + async fn inner_call(&self, req: Request<'_>, buf_size: usize) -> RawRpcResponse { + let (tx, mut rx) = mpsc::channel(buf_size); let id = req.id.clone(); let params = Params::new(req.params.map(|params| params.get())); let bounded_subs = BoundedSubscriptions::new(u32::MAX); - let subscription_permit = bounded_subs.acquire().expect("u32::MAX permits is sufficient; qed"); + let p1 = bounded_subs.acquire().expect("u32::MAX permits is sufficient; qed"); + let p2 = bounded_subs.acquire().expect("u32::MAX permits is sufficient; qed"); let response = match self.method(&req.method).map(|c| &c.callback) { - None => CallResponse::Call(MethodResponse::error(req.id, ErrorObject::from(ErrorCode::MethodNotFound))), - Some(MethodKind::Sync(cb)) => CallResponse::Call((cb)(id, params, usize::MAX)), - Some(MethodKind::Async(cb)) => { - CallResponse::Call((cb)(id.into_owned(), params.into_owned(), 0, usize::MAX).await) - } + None => MethodResponse::error(req.id, ErrorObject::from(ErrorCode::MethodNotFound)), + Some(MethodKind::Sync(cb)) => (cb)(id, params, usize::MAX), + Some(MethodKind::Async(cb)) => (cb)(id.into_owned(), params.into_owned(), 0, usize::MAX).await, Some(MethodKind::Subscription(cb)) => { - let conn_state = ConnState { conn_id: 0, id_provider: &RandomIntegerIdProvider, subscription_permit }; - let res = (cb)(id, params, MethodSink::new(tx), conn_state).await; - - CallResponse::Subscribe(res) + let conn_state = + ConnState { conn_id: 0, id_provider: &RandomIntegerIdProvider, subscription_permit: p1 }; + let res = (cb)(id, params, MethodSink::new(tx.clone()), conn_state).await; + + // This message is not used because it's used for metrics so we discard in other to + // not read once this is used for subscriptions. + // + // The same information is part of `res` above. + let _ = rx.recv().await.expect("Every call must at least produce one response; qed"); + + match res { + SubscriptionAnswered::Yes(r) => r, + SubscriptionAnswered::No(r) => r, + } } - Some(MethodKind::Unsubscription(cb)) => CallResponse::Call((cb)(id, params, 0, usize::MAX)), + Some(MethodKind::Unsubscription(cb)) => (cb)(id, params, 0, usize::MAX), }; tracing::trace!("[Methods::inner_call] Method: {}, response: {:?}", req.method, response); - response + (response, rx, p2, tx) } /// Helper to create a subscription on the `RPC module` without having to spin up a server. @@ -557,26 +562,22 @@ impl Methods { ) -> Result { let params = params.to_rpc_params()?; let req = Request::new(sub_method.into(), params.as_ref().map(|p| p.as_ref()), Id::Number(0)); - let (tx, mut rx) = mpsc::channel(buf_size); tracing::trace!("[Methods::subscribe] Method: {}, params: {:?}", sub_method, params); - let call_resp = self.inner_call(req, tx.clone()).await; - let response = call_resp.as_response(); + let (resp, rx, permit, tx) = self.inner_call(req, buf_size).await; - let subscription_response = match serde_json::from_str::>(&response.result) { + let subscription_response = match serde_json::from_str::>(&resp.result) { Ok(r) => r, - Err(_) => match serde_json::from_str::(&response.result) { + Err(_) => match serde_json::from_str::(&resp.result) { Ok(err) => return Err(Error::Call(CallError::Custom(err.error_object().clone().into_owned()))), Err(err) => return Err(err.into()), }, }; let sub_id = subscription_response.result.into_owned(); - // throw away the response to the subscription call. - _ = rx.recv().await; - Ok(Subscription { sub_id, rx, tx: MethodSink::new(tx) }) + Ok(Subscription { sub_id, rx, tx: MethodSink::new(tx), _permit: permit }) } /// Returns an `Iterator` with all the method names registered on this server. @@ -1155,6 +1156,7 @@ pub struct Subscription { tx: MethodSink, rx: mpsc::Receiver, sub_id: RpcSubscriptionId<'static>, + _permit: SubscriptionPermit, } impl Subscription { diff --git a/server/src/tests/ws.rs b/server/src/tests/ws.rs index 8d60174256..94f4f612bb 100644 --- a/server/src/tests/ws.rs +++ b/server/src/tests/ws.rs @@ -728,7 +728,6 @@ async fn ws_server_backpressure_works() { while now.elapsed() < std::time::Duration::from_secs(10) { msg = client.receive().with_default_timeout().await.unwrap().unwrap(); - tracing::info!("{msg}"); if let Ok(sub_notif) = serde_json::from_str::>(&msg) { match sub_notif.params.result { 1 if seen_backpressure_item => { diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 225a490b11..0c7397c8c6 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -213,7 +213,6 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData logger.on_call(name, params.clone(), logger::MethodKind::Subscription, TransportProtocol::WebSocket); if let Some(p) = bounded_subscriptions.acquire() { - tracing::info!("{:?}", p); let conn_state = ConnState { conn_id, id_provider, subscription_permit: p }; let response = callback(id.clone(), params, sink.clone(), conn_state).await; CallResponse::Subscribe(response) diff --git a/tests/tests/proc_macros.rs b/tests/tests/proc_macros.rs index 4125a3d648..d9997bee94 100644 --- a/tests/tests/proc_macros.rs +++ b/tests/tests/proc_macros.rs @@ -210,7 +210,6 @@ mod rpc_impl { // Use generated implementations of server and client. use jsonrpsee::core::params::{ArrayParams, ObjectParams}; use rpc_impl::{RpcClient, RpcServer, RpcServerImpl}; -use tokio::sync::mpsc; pub async fn server() -> SocketAddr { let server = ServerBuilder::default().build("127.0.0.1:0").await.unwrap(); @@ -261,8 +260,6 @@ async fn macro_param_parsing() { async fn macro_optional_param_parsing() { let module = RpcServerImpl.into_rpc(); - let (tx, mut rx) = mpsc::channel(1); - // Optional param omitted at tail let res: String = module.call("foo_optional_params", [42_u64, 70]).await.unwrap(); assert_eq!(&res, "Called with: 42, Some(70), None"); @@ -273,12 +270,11 @@ async fn macro_optional_param_parsing() { assert_eq!(&res, "Called with: 42, None, Some(70)"); // Named params using a map - module - .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_optional_params","params":{"a":22,"c":50},"id":0}"#, tx) + let (resp, _) = module + .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_optional_params","params":{"a":22,"c":50},"id":0}"#, 1) .await .unwrap(); - let resp = rx.recv().await.unwrap(); - assert_eq!(resp, r#"{"jsonrpc":"2.0","result":"Called with: 22, None, Some(50)","id":0}"#); + assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":"Called with: 22, None, Some(50)","id":0}"#); } #[tokio::test] @@ -296,25 +292,20 @@ async fn macro_zero_copy_cow() { let module = RpcServerImpl.into_rpc(); - let (tx, mut rx) = mpsc::channel(16); - - module - .raw_json_request( - r#"{"jsonrpc":"2.0","method":"foo_zero_copy_cow","params":["foo", "bar"],"id":0}"#, - tx.clone(), - ) + let (resp, _) = module + .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_zero_copy_cow","params":["foo", "bar"],"id":0}"#, 1) .await .unwrap(); // std::borrow::Cow always deserialized to owned variant here - assert_eq!(rx.recv().await.unwrap(), r#"{"jsonrpc":"2.0","result":"Zero copy params: false, true","id":0}"#); + assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":"Zero copy params: false, true","id":0}"#); // serde_json will have to allocate a new string to replace `\t` with byte 0x09 (tab) - module - .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_zero_copy_cow","params":["\tfoo", "\tbar"],"id":0}"#, tx) + let (resp, _) = module + .raw_json_request(r#"{"jsonrpc":"2.0","method":"foo_zero_copy_cow","params":["\tfoo", "\tbar"],"id":0}"#, 1) .await .unwrap(); - assert_eq!(rx.recv().await.unwrap(), r#"{"jsonrpc":"2.0","result":"Zero copy params: false, false","id":0}"#); + assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":"Zero copy params: false, false","id":0}"#); } // Disabled on MacOS as GH CI timings on Mac vary wildly (~100ms) making this test fail. diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index b0735c9410..7b2d8315ff 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -356,21 +356,20 @@ async fn subscribe_unsubscribe_without_server() { async fn subscribe_and_assert(module: &RpcModule<()>) { let sub = module.subscribe_unbounded("my_sub", EmptyServerParams::new()).await.unwrap(); let ser_id = serde_json::to_string(sub.subscription_id()).unwrap(); - let (tx, mut rx) = mpsc::channel(1); assert!(!sub.is_closed()); // Unsubscribe should be valid. let unsub_req = format!("{{\"jsonrpc\":\"2.0\",\"method\":\"my_unsub\",\"params\":[{}],\"id\":1}}", ser_id); - module.raw_json_request(&unsub_req, tx.clone()).await.unwrap(); + let (resp, _) = module.raw_json_request(&unsub_req, 1).await.unwrap(); - assert_eq!(rx.recv().await.unwrap(), r#"{"jsonrpc":"2.0","result":true,"id":1}"#); + assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":true,"id":1}"#); // Unsubscribe already performed; should be error. let unsub_req = format!("{{\"jsonrpc\":\"2.0\",\"method\":\"my_unsub\",\"params\":[{}],\"id\":1}}", ser_id); - module.raw_json_request(&unsub_req, tx).await.unwrap(); + let (resp, _) = module.raw_json_request(&unsub_req, 2).await.unwrap(); - assert_eq!(rx.recv().await.unwrap(), r#"{"jsonrpc":"2.0","result":false,"id":1}"#); + assert_eq!(resp.result, r#"{"jsonrpc":"2.0","result":false,"id":1}"#); } let sub1 = subscribe_and_assert(&module); From 1a18f9bda5d734849cd933cfd95a844f22efd08e Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 8 Feb 2023 17:22:56 +0100 Subject: [PATCH 59/60] subscribe_bounded -> subscribe --- core/src/server/rpc_module.rs | 6 +++--- tests/tests/rpc_module.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index e74dabcd0c..7fdeb5b252 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -550,11 +550,11 @@ impl Methods { /// } /// ``` pub async fn subscribe_unbounded(&self, sub_method: &str, params: impl ToRpcParams) -> Result { - self.subscribe_bounded(sub_method, params, u32::MAX as usize).await + self.subscribe(sub_method, params, u32::MAX as usize).await } - /// Similar to [`Methods::subscribe_unbounded`] but it's using a bounded channel. - pub async fn subscribe_bounded( + /// Similar to [`Methods::subscribe_unbounded`] but it's using a bounded channel and the buffer capacity must be provided. + pub async fn subscribe( &self, sub_method: &str, params: impl ToRpcParams, diff --git a/tests/tests/rpc_module.rs b/tests/tests/rpc_module.rs index 7b2d8315ff..01f46447a7 100644 --- a/tests/tests/rpc_module.rs +++ b/tests/tests/rpc_module.rs @@ -468,7 +468,7 @@ async fn bounded_subscription_works() { // create a bounded subscription and don't poll it // after 3 items has been produced messages will be dropped. - let mut sub = module.subscribe_bounded("my_sub", EmptyServerParams::new(), 3).await.unwrap(); + let mut sub = module.subscribe("my_sub", EmptyServerParams::new(), 3).await.unwrap(); // assert that some items couldn't be sent. assert_eq!(rx.recv().await, Some("Full".to_string())); From 039a0d11a0dbeafe279cea8582ea61bfa118ca54 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 8 Feb 2023 17:35:13 +0100 Subject: [PATCH 60/60] CallResponse -> CallOrSubscription --- core/src/server/rpc_module.rs | 12 ++++++------ server/src/transport/ws.rs | 31 +++++++++++++++++-------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/core/src/server/rpc_module.rs b/core/src/server/rpc_module.rs index 7fdeb5b252..dbe7206e4d 100644 --- a/core/src/server/rpc_module.rs +++ b/core/src/server/rpc_module.rs @@ -195,20 +195,20 @@ type Subscribers = Arc &MethodResponse { match &self { - Self::Subscribe(r) => match r { + Self::Subscription(r) => match r { SubscriptionAnswered::Yes(r) => r, SubscriptionAnswered::No(r) => r, }, @@ -216,10 +216,10 @@ impl CallResponse { } } - /// Convert the `CallResponse` to JSON-RPC response. + /// Convert the `CallOrSubscription` to JSON-RPC response. pub fn into_response(self) -> MethodResponse { match self { - Self::Subscribe(r) => match r { + Self::Subscription(r) => match r { SubscriptionAnswered::Yes(r) => r, SubscriptionAnswered::No(r) => r, }, diff --git a/server/src/transport/ws.rs b/server/src/transport/ws.rs index 0c7397c8c6..479f23d639 100644 --- a/server/src/transport/ws.rs +++ b/server/src/transport/ws.rs @@ -14,7 +14,7 @@ use hyper::upgrade::Upgraded; use jsonrpsee_core::server::helpers::{ prepare_error, BatchResponse, BatchResponseBuilder, BoundedSubscriptions, MethodResponse, MethodSink, }; -use jsonrpsee_core::server::rpc_module::{CallResponse, ConnState, MethodKind, Methods, SubscriptionAnswered}; +use jsonrpsee_core::server::rpc_module::{CallOrSubscription, ConnState, MethodKind, Methods, SubscriptionAnswered}; use jsonrpsee_core::tracing::{rx_log_from_json, tx_log_from_str}; use jsonrpsee_core::traits::IdProvider; use jsonrpsee_core::{Error, JsonRawValue}; @@ -151,17 +151,20 @@ pub(crate) async fn process_batch_request(b: Batch<'_, L>) -> Option< } } -pub(crate) async fn process_single_request(data: Vec, call: CallData<'_, L>) -> CallResponse { +pub(crate) async fn process_single_request(data: Vec, call: CallData<'_, L>) -> CallOrSubscription { if let Ok(req) = serde_json::from_slice::(&data) { execute_call_with_tracing(req, call).await } else { let (id, code) = prepare_error(&data); - CallResponse::Call(MethodResponse::error(id, ErrorObject::from(code))) + CallOrSubscription::Call(MethodResponse::error(id, ErrorObject::from(code))) } } #[instrument(name = "method_call", fields(method = req.method.as_ref()), skip(call, req), level = "TRACE")] -pub(crate) async fn execute_call_with_tracing<'a, L: Logger>(req: Request<'a>, call: CallData<'_, L>) -> CallResponse { +pub(crate) async fn execute_call_with_tracing<'a, L: Logger>( + req: Request<'a>, + call: CallData<'_, L>, +) -> CallOrSubscription { execute_call(req, call).await } @@ -170,7 +173,7 @@ pub(crate) async fn execute_call_with_tracing<'a, L: Logger>(req: Request<'a>, c /// /// Returns `(MethodResponse, None)` on every call that isn't a subscription /// Otherwise `(MethodResponse, Some(PendingSubscriptionCallTx)`. -pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData<'_, L>) -> CallResponse { +pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData<'_, L>) -> CallOrSubscription { let CallData { methods, max_response_body_size, @@ -193,12 +196,12 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData None => { logger.on_call(name, params.clone(), logger::MethodKind::Unknown, TransportProtocol::WebSocket); let response = MethodResponse::error(id, ErrorObject::from(ErrorCode::MethodNotFound)); - CallResponse::Call(response) + CallOrSubscription::Call(response) } Some((name, method)) => match &method.inner() { MethodKind::Sync(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::MethodCall, TransportProtocol::WebSocket); - CallResponse::Call((callback)(id, params, max_response_body_size as usize)) + CallOrSubscription::Call((callback)(id, params, max_response_body_size as usize)) } MethodKind::Async(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::MethodCall, TransportProtocol::WebSocket); @@ -207,7 +210,7 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData let params = params.into_owned(); let response = (callback)(id, params, conn_id, max_response_body_size as usize).await; - CallResponse::Call(response) + CallOrSubscription::Call(response) } MethodKind::Subscription(callback) => { logger.on_call(name, params.clone(), logger::MethodKind::Subscription, TransportProtocol::WebSocket); @@ -215,11 +218,11 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData if let Some(p) = bounded_subscriptions.acquire() { let conn_state = ConnState { conn_id, id_provider, subscription_permit: p }; let response = callback(id.clone(), params, sink.clone(), conn_state).await; - CallResponse::Subscribe(response) + CallOrSubscription::Subscription(response) } else { let response = MethodResponse::error(id, reject_too_many_subscriptions(bounded_subscriptions.max())); - CallResponse::Call(response) + CallOrSubscription::Call(response) } } MethodKind::Unsubscription(callback) => { @@ -227,7 +230,7 @@ pub(crate) async fn execute_call<'a, L: Logger>(req: Request<'a>, call: CallData // Don't adhere to any resource or subscription limits; always let unsubscribing happen! let result = callback(id, params, conn_id, max_response_body_size as usize); - CallResponse::Call(result) + CallOrSubscription::Call(result) } }, }; @@ -365,14 +368,14 @@ pub(crate) async fn background_task( }; match process_single_request(data, call).await { - CallResponse::Subscribe(SubscriptionAnswered::Yes(r)) => { + CallOrSubscription::Subscription(SubscriptionAnswered::Yes(r)) => { logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); } - CallResponse::Subscribe(SubscriptionAnswered::No(r)) => { + CallOrSubscription::Subscription(SubscriptionAnswered::No(r)) => { logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); sink_permit.send_raw(r.result); } - CallResponse::Call(r) => { + CallOrSubscription::Call(r) => { logger.on_response(&r.result, request_start, TransportProtocol::WebSocket); sink_permit.send_raw(r.result); }