diff --git a/http-server/Cargo.toml b/http-server/Cargo.toml index 67965be1e9..3c869b45bc 100644 --- a/http-server/Cargo.toml +++ b/http-server/Cargo.toml @@ -26,3 +26,4 @@ unicase = "2.6.0" [dev-dependencies] env_logger = "0.9" jsonrpsee-test-utils = { path = "../test-utils" } +jsonrpsee = { path = "../jsonrpsee", features = ["full"] } diff --git a/http-server/src/server.rs b/http-server/src/server.rs index e4c67163cf..920b8d5808 100644 --- a/http-server/src/server.rs +++ b/http-server/src/server.rs @@ -84,8 +84,11 @@ impl Builder { self } - /// Register a new resource kind. Errors if `label` is already registered, or if number of - /// registered resources would exceed 8. + /// 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) + /// for details. pub fn register_resource(mut self, label: &'static str, capacity: u16, default: u16) -> Result { self.resources.register(label, capacity, default)?; diff --git a/proc-macros/src/attributes.rs b/proc-macros/src/attributes.rs index df0d1b01b8..c90ffd6141 100644 --- a/proc-macros/src/attributes.rs +++ b/proc-macros/src/attributes.rs @@ -25,7 +25,7 @@ // DEALINGS IN THE SOFTWARE. use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree}; -use std::fmt; +use std::{fmt, iter}; use syn::parse::{Parse, ParseStream, Parser}; use syn::punctuated::Punctuated; use syn::{spanned::Spanned, Attribute, Error, Token}; @@ -51,15 +51,14 @@ impl Parse for Argument { fn parse(input: ParseStream) -> syn::Result { let label = input.parse()?; - let mut tokens = TokenStream2::new(); let mut scope = 0usize; // Need to read to till either the end of the stream, // or the nearest comma token that's not contained // inside angle brackets. - loop { + let tokens = iter::from_fn(move || { if scope == 0 && input.peek(Token![,]) { - break; + return None; } if input.peek(Token![<]) { @@ -68,11 +67,9 @@ impl Parse for Argument { scope = scope.saturating_sub(1); } - match input.parse::() { - Ok(token) => tokens.extend([token]), - Err(_) => break, - } - } + input.parse::().ok() + }) + .collect(); Ok(Argument { label, tokens }) } diff --git a/utils/src/server/mod.rs b/utils/src/server/mod.rs index 80811d82d0..fe1a99277b 100644 --- a/utils/src/server/mod.rs +++ b/utils/src/server/mod.rs @@ -28,7 +28,7 @@ /// Helpers. pub mod helpers; -/// Resource limiting helpers +/// 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/utils/src/server/resource_limiting.rs b/utils/src/server/resource_limiting.rs index 1c11313f9a..71e02693ec 100644 --- a/utils/src/server/resource_limiting.rs +++ b/utils/src/server/resource_limiting.rs @@ -1,3 +1,94 @@ +// 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 [`WsServerBuilder::register_resource`](../../../jsonrpsee_ws_server/struct.WsServerBuilder.html#method.register_resource) +//! or [`HttpServerBuilder::register_resource`](../../../jsonrpsee_ws_server/struct.WsServerBuilder.html#method.register_resource) method +//! for the WebSocket and HTTP server respectively. +//! +//! 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::{types::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, types::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 arrayvec::ArrayVec; diff --git a/utils/src/server/rpc_module.rs b/utils/src/server/rpc_module.rs index 1b7a022a96..fb1101ad77 100644 --- a/utils/src/server/rpc_module.rs +++ b/utils/src/server/rpc_module.rs @@ -239,7 +239,13 @@ impl Methods { None => return Err(Error::ResourceNameNotFoundForMethod(label, method_name)), }; - map[idx] = units; + // 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); @@ -254,7 +260,7 @@ impl Methods { Arc::make_mut(&mut self.callbacks) } - /// Merge two [`Methods`]'s by adding all [`MethodKind`]s from `other` into `self`. + /// Merge two [`Methods`]'s by adding all [`MethodCallback`]s from `other` into `self`. /// Fails if any of the methods in `other` is present already. pub fn merge(&mut self, other: impl Into) -> Result<(), Error> { let mut other = other.into(); diff --git a/ws-server/Cargo.toml b/ws-server/Cargo.toml index 6e9612b8e6..8329b8786a 100644 --- a/ws-server/Cargo.toml +++ b/ws-server/Cargo.toml @@ -24,3 +24,4 @@ tokio-util = { version = "0.6", features = ["compat"] } anyhow = "1" env_logger = "0.9" jsonrpsee-test-utils = { path = "../test-utils" } +jsonrpsee = { path = "../jsonrpsee", features = ["full"] } diff --git a/ws-server/src/server.rs b/ws-server/src/server.rs index f24bfc7b72..8b96a857f0 100644 --- a/ws-server/src/server.rs +++ b/ws-server/src/server.rs @@ -402,11 +402,13 @@ impl Builder { self } - /// Register a new resource kind. Errors if `label` is already registered, or if number of - /// registered resources would exceed 8. + /// 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) + /// for details. pub fn register_resource(mut self, label: &'static str, capacity: u16, default: u16) -> Result { self.resources.register(label, capacity, default)?; - Ok(self) }