-
Notifications
You must be signed in to change notification settings - Fork 177
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
Conversation
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>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
core/src/server/rpc_module.rs
Outdated
@@ -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)?; |
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.
can you explain this?
these are still required AFAIU, if any of the method names are already registered we should still fire an 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.
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 😄
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.
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" :)
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 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!
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 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.
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
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 |
Is this PR still valid and wants to be merged then? Wondering whether to look over it or not :) |
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. |
tests/tests/resource_limiting.rs
Outdated
// 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"), | ||
); |
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.
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?
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.
(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"?"
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 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 |
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.
: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.
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)?
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
This PR ensures that resource limiting is available for
Subscriptions
.The
ResourceGuard
wrapper is kept alive for as long as aSubscriptionSink
is utilized to properlyincrement 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 immediatelyafter 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 .