-
Notifications
You must be signed in to change notification settings - Fork 172
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
Resource Limiting #500
Conversation
@@ -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> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, lovely
There was a problem hiding this 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 <[email protected]> * syn-up all the things, handle resources on methods Co-authored-by: Niklas Adolfsson <[email protected]>
Co-authored-by: David <[email protected]> Co-authored-by: Niklas Adolfsson <[email protected]>
assert_server_busy(fail_mem); | ||
|
||
// Client being active prevents the server from shutting down?! | ||
drop(client); |
There was a problem hiding this comment.
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....
There was a problem hiding this comment.
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 🙃
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@@ -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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this 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
There was a problem hiding this 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![=], |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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>), |
There was a problem hiding this comment.
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?
There was a problem hiding this 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
There was a problem hiding this 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:
- updated examples and docs showing the macro syntax alongside the server setup
- resources and units are dimensionless (no intrinsic meaning, they mean what you want them to mean)
- 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)
/// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// 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`. |
/// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Optional prefix for RPC namespace. | |
/// Optional prefix for the RPC namespace. |
sleep(Duration::from_millis(50)).await; | ||
Ok("hello memory hog") | ||
})? | ||
.resource("CPU", 0)? |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
Closes #203 (details in the issue).
Remaining TODOs: