-
Notifications
You must be signed in to change notification settings - Fork 94
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
Decode with mem limit #602
Conversation
Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
This reverts commit 43f08d7.
Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
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.
Thank you for the suggestion and for opening the PR ! The high level idea looks good ! I just left a couple of comments.
Also could you please let me know how you would prefer to move forward from here ? Would you have time to continue this PR ? If not I would be happy to take it since we need it for the Bridges + XCM.
<Compact<u32>>::decode(input) | ||
.and_then(move |Compact(len)| decode_vec_with_len(input, len as usize)) | ||
} | ||
} | ||
|
||
// Mark vec as MemLimited since we track the allocated memory: | ||
impl<T: Decode> crate::memory_limit::DecodeMemLimit for Vec<T> { } |
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 we need to do impl<T: crate::memory_limit::DecodeMemLimit> crate::memory_limit::DecodeMemLimit for Vec<T> { }
. Otherwise I can call decode_with_mem_limit
for example on a Vec<Box<_>>
which shouldn't support this yet.
|
||
impl MemLimit { | ||
/// Try to allocate a contiguous chunk of memory. | ||
pub fn try_alloc(&mut self, size: usize) -> 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.
I would rather just provide the type and do std::mem::size_of()
here which should account for the alignment automatically.
@@ -85,6 +85,11 @@ pub trait Input { | |||
/// This is called when decoding reference-based type is finished. | |||
fn ascend_ref(&mut self) {} | |||
|
|||
/// Try to allocate a contiguous chunk of memory of `size` bytes. | |||
fn try_alloc(&mut self, size: usize) -> 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.
Nit: It looks like we won't really allocate memory here, but only keep track of it. So I would rename this to something like note_alloc()
. Or even something more generic like on_before_decode()
/// | ||
/// Should be used as a marker for types that do the memory tracking in their `decode` implementation with [`Input::try_alloc`]. | ||
pub trait DecodeMemLimit: Decode + Sized { | ||
fn decode_with_mem_limit<I: Input>(limit: MemLimit, input: &mut I) -> Result<Self, 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.
This approach seems a bit limiting since we can only do decode_with_mem_limit
or decode_with_depth_limit
. Looks like we can't combine them. It would be nice to be able to do something more generic. For example something like:
decode_with_check(input: &mut I, check: FnMut(DecodeContext) -> Result<(), Error>)
where DecodeContext
would be something like
DecodeContext {
depth: u32,
used_memory: usize,
...
}
This way we could check both for depth and memory limit at the same time. Maybe for other parameters as well. Also would be nice to be able to keep track of the number of objects of a certain type that we encounter recursively while decoding since this is also something that we needed in Polkadot.
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 like we can't combine them.
We can. Which is why i chose this approach. These functions are just wrappers, but there is nobody stopping you from doing:
let mut input = MemTrackingInput { inner: DepthTrackingInput { inner: .. }};
T::decode(&mut input);
But it wont have any meaning without requiring T: DecodeMemLimit
.
This way we could check both for depth and memory limit at the same time. Maybe for other parameters as well. Also would be nice to be able to keep track of the number of objects of a certain type that we encounter recursively while decoding since this is also something that we needed in Polkadot.
Yea, i had some decode_with_context<C>(..)
implemented before, but it makes this not such an opt-in.
For example when some type only wants to track recursion and another only size. If we otherwise enforce this, it would be a big breaking change.
The breaking change would occur in the case where we make the Parameter
type of a FRAME extrinsic require this.
/// the most fitting name. | ||
pub enum MemAlignment { | ||
/// Round up to the next power of two. | ||
NextPowerOfTwo, |
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.
Note: Currently when using this with a Vector, we align each element individually.
But a Vec allocates all elements in a chunk through the WASM bump allocator. So what we have to do is round(sum(e...))
over all elements instead of sum(round(e)..)
.
Otherwise it would round each element size up to the next power of two, but the vector may still allocate more since it aligns with the sum of the sizes of all elements.
I've been thinking about this some more and I'm not sure if this solution is feasible. My main concern is related to enums. For example, for the following enum:
I don't know if we can check the type of each field in an enum variant and call Another concern is that for structures like Another problem would be if we have skipped fields that consume significant memory. For example a Another issue is that here we do Also, since I was looking on these, I think there are some issues with the
I'm not sure. Maybe I'm missing something. @ggwpez , @bkchr what do you think ? |
We'll limit this to tracking only heap size, but we want to make it type safe. Tracked as part of: #609 Will close this draft for the moment and will open a separate PR later |
Adds a
try_alloc
function to theInput
trait to track allocations. This makes it necessary to implement it in thedecode
any type, but is not type checked. Currently it is only implemented for vectors.Some tests are in place to showcase the usage.
This MR adds functionality to restrict the memory usage while decoding a type.Some tests show how to use it with nested types or tuples, but mostly a PoC to evaluate the approach.
We could also do it like the
DecodeLimit
and track the memory consumption in theInput
stream, but it cannot be enforced by the type system.PS: I will change it to track the limit in the
Input
, as it allows to also track the other limits (like recursion depth) at the expense of not being type checked.Changes
Adds a generic
DecodeWithContext
that allows adecode_with_context
call to pass down some context when decoding.This new trait is then used together withMemLimit
such thatVec
implementsDecodeWithContext<MemLimit>
.There is a blanket in place that converts
DecodeWithContext<MemLimit>
toDecodeWithMemLimit
, and therebyte,Vec
is nowDecodeWithMemLimit
.