Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document resource limiting #510

Merged
merged 14 commits into from
Oct 12, 2021
1 change: 1 addition & 0 deletions http-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
7 changes: 5 additions & 2 deletions http-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, Error> {
self.resources.register(label, capacity, default)?;

Expand Down
15 changes: 6 additions & 9 deletions proc-macros/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -51,15 +51,14 @@ impl Parse for Argument {
fn parse(input: ParseStream) -> syn::Result<Self> {
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![<]) {
Expand All @@ -68,11 +67,9 @@ impl Parse for Argument {
scope = scope.saturating_sub(1);
}

match input.parse::<TokenTree>() {
Ok(token) => tokens.extend([token]),
Err(_) => break,
}
}
input.parse::<TokenTree>().ok()
})
.collect();

Ok(Argument { label, tokens })
}
Expand Down
2 changes: 1 addition & 1 deletion utils/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
maciejhirsz marked this conversation as resolved.
Show resolved Hide resolved
/// JSON-RPC "modules" group sets of methods that belong together and handles method/subscription registration.
pub mod rpc_module;
91 changes: 91 additions & 0 deletions utils/src/server/resource_limiting.rs
Original file line number Diff line number Diff line change
@@ -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`.
//!
maciejhirsz marked this conversation as resolved.
Show resolved Hide resolved
//! 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;
Expand Down
10 changes: 8 additions & 2 deletions utils/src/server/rpc_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<Methods>) -> Result<(), Error> {
let mut other = other.into();
Expand Down
1 change: 1 addition & 0 deletions ws-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
8 changes: 5 additions & 3 deletions ws-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Copy link
Collaborator

@jsdw jsdw Oct 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing that worries me about these duplicated docs is keeping them both in sync in the event that things change. I don't think that there is a nice way to share documentation though is there?

I'd be tempted to have one version of the docs link to the other version eg something like

/// Register a new reousrce kind. See [`crate::http_server::Builder::register_resource()`] for example usage.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that there is a nice way to share documentation though is there?

I don't know of a way either and it's quite common to see "look over here for details" in rustdocs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know of a way either and it's quite common to see "look over here for details" in rustdocs.

Yes, that's what I meant at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still have the duplicated comments on these, but the bulk of it has been moved to the resource_limiting module, link included, so I reckon this is resolved now?

/// 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, Error> {
self.resources.register(label, capacity, default)?;

Ok(self)
}

Expand Down