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

Resource Limiting #500

Merged
merged 45 commits into from
Oct 11, 2021
Merged

Resource Limiting #500

merged 45 commits into from
Oct 11, 2021

Conversation

maciejhirsz
Copy link
Contributor

@maciejhirsz maciejhirsz commented Oct 1, 2021

Closes #203 (details in the issue).

Remaining TODOs:

  • proc macros.
  • HTTP server.

@@ -68,9 +70,18 @@ impl Server {
}

/// Start responding to connections requests. This will block current thread until the server is stopped.
pub async fn start(self, methods: impl Into<Methods>) {
pub fn start(self, methods: impl Into<Methods>) -> Result<StopHandle, Error> {
Copy link
Member

Choose a reason for hiding this comment

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

ah, lovely

Copy link
Member

@niklasad1 niklasad1 left a comment

Choose a reason for hiding this comment

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

looks really promising, I will have a look tomorrow when my head is fresh...

* Squashed macros WIP

* bae-less macros!

* Make things simpler and more readable

* Some comments and DRY aliases parsing

* Naming things is hard

* Respan is no longer needed

* Simpler Arguments

* Remove stale code

* Apply suggestions from code review

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>

* syn-up all the things, handle resources on methods

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
@maciejhirsz maciejhirsz changed the title [WIP] Resource Limiting Resource Limiting Oct 8, 2021
assert_server_busy(fail_mem);

// Client being active prevents the server from shutting down?!
drop(client);
Copy link
Member

Choose a reason for hiding this comment

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

I think the server will be stopped even if the client is connected but not really important here....

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should do that, but if you remove the drop the test runs forever, so it never actually does shut down 🙃

Copy link
Member

Choose a reason for hiding this comment

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

ah I see, do you mind creating an issue for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, will do

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> {
Copy link
Member

Choose a reason for hiding this comment

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

capacity a u16 is very flexible and works for all use-cases I think.

However, I wonder if we could provide some more high level method that does something more human friendly as percent (with capacity as 100) or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We talked about it with @dvdplm at the retreat. The problem with % is that if you add hardware you can easily reconfigure max capacity, where as going in and adjusting every method to lower cpu or memory % would be quite a hassle.

ws-server/src/server.rs Outdated Show resolved Hide resolved
@@ -59,7 +58,7 @@ async fn server() -> SocketAddr {
/// other: `invalid_params` (always returns `CallError::InvalidParams`), `call_fail` (always returns
/// `CallError::Failed`), `sleep_for` Returns the address together with handles for server future
/// and server stop.
async fn server_with_handles() -> (SocketAddr, JoinHandle<()>, StopHandle) {
async fn server_with_handles() -> (SocketAddr, StopHandle) {
Copy link
Member

Choose a reason for hiding this comment

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

👍

Copy link
Member

@niklasad1 niklasad1 left a comment

Choose a reason for hiding this comment

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

nice work, looks clean as always

Copy link
Member

@TarikGul TarikGul left a comment

Choose a reason for hiding this comment

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

👍

#[derive(Debug, Clone)]
pub struct Resource {
pub name: syn::LitStr,
pub assign: Token![=],
Copy link
Collaborator

@jsdw jsdw Oct 11, 2021

Choose a reason for hiding this comment

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

Is this actually needed in the struct (it's just here because you need to parse it I guess)? (I think I can see that it might be cleaner to have here in any case)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's not necessary for anything, but it made both the parse cleaner and the purpose of it more obvious since it maps 1:1 to what you see in the macro use.

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<str>),
Copy link
Collaborator

@jsdw jsdw Oct 11, 2021

Choose a reason for hiding this comment

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

Bike shedding: I wonder whether something like ResourceUninitializedForMethod might be a touch more consistent with the above resource related (and prefixed) errors?

Copy link
Collaborator

@jsdw jsdw left a comment

Choose a reason for hiding this comment

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

Looks great!

Definitely some of the more readable proc macro code I've come across, too :D

Copy link
Contributor

@dvdplm dvdplm left a comment

Choose a reason for hiding this comment

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

I think the proc_macro example should include resource limiting and the proc macro docs need a fairly chunky piece of docs covering:

  1. updated examples and docs showing the macro syntax alongside the server setup
  2. resources and units are dimensionless (no intrinsic meaning, they mean what you want them to mean)
  3. motivation for setting up resources on the server (as opposed to on the modules), as this creates a somewhat awkward API, where modules declare resources, but the resources themselves are set up and configured when building the server.

(Also, and not introduced in this PR, the proc_macro example prints [2021-10-11T09:56:59Z ERROR jsonrpsee_ws_client::helpers] Dropping subscription Num(8470526752200743) error: TrySendError { kind: Disconnected } when ran with cargo run --example=proc_macro which imo isn't very user friendly)

Comment on lines +135 to +137
/// Switch denoting that server trait must be generated.
/// Assuming that trait to which attribute is applied is named `Foo`, the generated
/// server trait will have `FooServer` name.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Switch denoting that server trait must be generated.
/// Assuming that trait to which attribute is applied is named `Foo`, the generated
/// server trait will have `FooServer` name.
/// Switch denoting that the server trait must be generated.
/// Assuming that the trait to which the attribute is applied is named `Foo`, the generated
/// server trait will be named `FooServer`.

Comment on lines +139 to +141
/// Switch denoting that client extension trait must be generated.
/// Assuming that trait to which attribute is applied is named `Foo`, the generated
/// client trait will have `FooClient` name.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Switch denoting that client extension trait must be generated.
/// Assuming that trait to which attribute is applied is named `Foo`, the generated
/// client trait will have `FooClient` name.
/// Switch denoting that the client extension trait must be generated.
/// Assuming that the trait to which the attribute is applied is named `Foo`, the generated
/// client trait will be named `FooClient`.

/// Assuming that trait to which attribute is applied is named `Foo`, the generated
/// client trait will have `FooClient` name.
pub(crate) needs_client: bool,
/// Optional prefix for RPC namespace.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Optional prefix for RPC namespace.
/// Optional prefix for the RPC namespace.

sleep(Duration::from_millis(50)).await;
Ok("hello memory hog")
})?
.resource("CPU", 0)?
Copy link
Contributor

Choose a reason for hiding this comment

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

Add comment explaining that this is the same as omitting the resource, i.e. that 0 is the default?

@@ -365,6 +396,14 @@ impl Builder {
self
}

/// Register a new resource kind. Errors if `label` is already registered, or if number of
/// registered resources would exceed 8.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please document the meaning of capacity and default.

I wonder if having a ResourceBuilder with .with_cap and .with_default methods would make the api more self-explanatory? Not a blocker but one to ponder.

@@ -273,7 +300,10 @@ async fn background_task(
// complete batch response back to the client over `tx`.
let (tx_batch, mut rx_batch) = mpsc::unbounded::<String>();

for fut in batch.into_iter().filter_map(|req| methods.execute(&tx_batch, req, conn_id)) {
for fut in batch
Copy link
Contributor

Choose a reason for hiding this comment

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

@maciejhirsz did we get a test&docs for this somewhere?

We had a conversation about this and came to the conclusion that while it's possible to calculate the batch "cost" up front, the semantics would become rather awkward and the overall behaviour of the server a bit hard to understand and reason about; on a busy server a batch could end up in a situation where it never gets enough resources to run or, conversely, a sequentially executed batch might block other requests from running.

@dvdplm dvdplm merged commit 518a615 into master Oct 11, 2021
@dvdplm dvdplm deleted the mh-resource-limiting branch October 11, 2021 10:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Resource limiting
5 participants