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

Add resource limiting for Subscriptions #786

Merged
merged 13 commits into from
Jun 14, 2022
Merged

Conversation

lexnv
Copy link
Contributor

@lexnv lexnv commented May 30, 2022

This PR ensures that resource limiting is available for Subscriptions.

The ResourceGuard wrapper is kept alive for as long as a SubscriptionSink is utilized to properly
increment and decrement the internal resources. Otherwise, the resource limiting would have the
same effect as for sync methods, which would acquire the resources and drop them immediately
after the function was called.

This implies that subscription registration behaves like normal methods, returning a resource-builder
that can accept limits. To ensure proper feature exposure, the proc-macro generation is extended
to receive resources while generating subscriptions.

While at it, extend the resource limiting tests with the subscription limit.

Closes #766 .

lexnv added 7 commits May 30, 2022 15:26
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Registering a subscription returns the subscription'
callback wrapped into a `MethodResourcesBuilder` for resource
limiting purposes.

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
@lexnv lexnv requested a review from a team as a code owner May 30, 2022 16:29
@lexnv lexnv self-assigned this May 30, 2022
@lexnv lexnv requested a review from niklasad1 May 30, 2022 16:31
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
@@ -689,39 +689,14 @@ impl<Context: Send + Sync + 'static> RpcModule<Context> {
return Err(Error::SubscriptionNameConflict(subscribe_method_name.into()));
}

self.methods.verify_method_name(subscribe_method_name)?;
Copy link
Member

@niklasad1 niklasad1 May 30, 2022

Choose a reason for hiding this comment

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

can you explain this?

these are still required AFAIU, if any of the method names are already registered we should still fire an error

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question, I've utilized the verify_and_insert method, which should still return error if the method is already registered. Given I've needed the callback reference back for subscription, I've picked the same registering pattern for subscription as well 😄

Copy link
Member

@niklasad1 niklasad1 May 30, 2022

Choose a reason for hiding this comment

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

The problem is that you check the subscription and unsubscription indiviudally.
Thus, if only the unsubscription fails we should still deny both.

For example, if the subscription is successful and the unsubscription fails you can't really unsubscribe 🤔

EDIT: Ok, I saw that you changed so unsubscriptions are performed first which "should" work but a bit "unexplicit" :)

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 see now what you mean! Indeed this was living the door open for having the unsubscriptions left initialized, have modified to use the previous way of first verifying the method's name. Good catch! Thanks!

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 overall good.

I don't remember exactly but this is tricky because often these subscriptions might be event based or periodic.

So once I register a subscription it's not really "static" as it depends on how often notifications are sent out or whether a given state has been triggered.

So the question is really whether the SubscriptionSink also should be part of the resource guard. I reckon that it might quite hard to reason about once one "register resources" on a subscription.

For example take state_subscribeStorage which depends heavily on the number of storage keys which is a parameter to the subscription, it's impossible to give a accurate static resource limit without knowing the number of storage keys. I suppose the amount data sent via the SubscriptionSink could be one way to determine that.

lexnv added 2 commits May 31, 2022 13:29
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
ws-server/src/server.rs Outdated Show resolved Hide resolved
@jsdw
Copy link
Collaborator

jsdw commented Jun 13, 2022

At the retreat it sounded like you guys decided now to add resource limiting for subscriptions owing to various things? Does that render this PR obsolete?

@niklasad1
Copy link
Member

niklasad1 commented Jun 13, 2022

At the retreat it sounded like you guys decided now to add resource limiting for subscriptions owing to various things? Does that render this PR obsolete?

Well, we have discussed this and implementing resource limiting on subscriptions properly with the "abstract user defined metrics" is just too hard.

The idea is to implement/investigate to do backpressure on the sink instead and kill resource limiting completely.

@jsdw
Copy link
Collaborator

jsdw commented Jun 13, 2022

Is this PR still valid and wants to be merged then? Wondering whether to look over it or not :)

@niklasad1
Copy link
Member

We can merge it anyway but as I pointed it out it won't well with "dynamic keys" but as long as we have resource limiting the PR makes sense.

Comment on lines 220 to 225
// 3 CPU units (manually set for subscriptions) per call, the sink is dropped immediately
let (pass1, pass2, pass3) = tokio::join!(
client.subscribe::<i32>("subscribe_hello", None, "unsubscribe_hello"),
client.subscribe::<i32>("subscribe_hello", None, "unsubscribe_hello"),
client.subscribe::<i32>("subscribe_hello", None, "unsubscribe_hello"),
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Am I right in thinking that even if there were 10 client.subscribes here, they would all be OK because something is dropped immediately, and there is no race condition where that might not always be true?

Copy link
Collaborator

Choose a reason for hiding this comment

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

(if so, could the comment be tweaked to say something like "the sink is dropped immediately, so no resources are claimed and all of the calls succeed"?"

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 believe that to be the case for this particular test.

We submit all the subscribe requests from one client to the server. The server handles each client on a dedicated background_task task. That means all our submitted subscribe requests get handled serially, on the same background task.
The subscription handling would: acquire + update + release resource table, call subscription callback, drop the sink immediately, acquire + update + release resource table.

The resource limiting would reject connections for multiple clients if they attempt to make a subscription call to a method that drops the sink immediately.

@@ -21,7 +21,7 @@
// 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 background_task WITH THE SOFTWARE OR THE USE OR OTHER
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
Copy link
Collaborator

Choose a reason for hiding this comment

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

:D

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 to me offhand! I do think that having a consistent resource limiting API across everything is good, and at the very least it allows subscriptions to say "this is the minimum amount of CPU/memory I need available in order to run", even if some subscriptions may use more (it reminds me of resources in kubernetes pods, where you can't know how much they could use, but providing some idea is better than not at all).

Given that you can have a "SUB" resource as in thexe examples, and use it to limit the max subscriptions, I wonder whether this will eventually lead to the explicit "max subscriptions" feature being removed (or at least rendered less useful, since this API is more powerful)?

lexnv added 2 commits June 14, 2022 14:59
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
@lexnv lexnv merged commit 01577da into master Jun 14, 2022
@lexnv lexnv deleted the 766_limit_subscription branch June 14, 2022 16: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]: add support for subscriptions
3 participants