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

Fix oom #2665

Merged
merged 3 commits into from
Mar 17, 2022
Merged

Fix oom #2665

merged 3 commits into from
Mar 17, 2022

Conversation

shargon
Copy link
Member

@shargon shargon commented Feb 26, 2022

Fix #2663

@fyrchik
Copy link
Contributor

fyrchik commented Feb 26, 2022

This solution restricts intermediate array size, even though the final output can be empty (e.g. the last level index can be out of range). I have implemented another solution (process value in a DFS fashion and restrict the size the real result to MaxOracleResponseSize), but this one is simpler to implement and covers all reasonable cases. Good job!

@@ -54,6 +55,13 @@ public class UT_JPath
["data"] = null,
};

[TestMethod]
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you also add a boundary test where the size of an intermediate array is approximately equal to the maximum allowed limit?

The size in case is width^depth (64^6 in this case). So tests for 2^10 (width 10, depth 2) and 32^2 (width 2, depth 32) should pass and result in an array of 1024 empty objects. Note, that the value itself should also be changed in this case (amount of [ should be equal to depth).

This will help to ensure neo-go stays compatible.

Copy link
Contributor

Choose a reason for hiding this comment

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

My bad, the second test shouldn't pass because of the depth limit.

{
List<JObject> results = new();
JPathToken token = DequeueToken(tokens);
if (token.Type != JPathTokenType.Identifier) throw new FormatException();
while (objects.Length > 0)
{
results.AddRange(objects.Where(p => p is not null).SelectMany(p => p.Properties).Where(p => p.Key == token.Content).Select(p => p.Value));
Descent(ref objects, ref maxDepth);
Descent(ref objects, ref maxDepth, maxObjects);
if (results.Count > maxObjects) throw new InvalidOperationException(nameof(maxObjects));
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest to make the exception more precise, use ArgumentOutOfRangeException or OutOfMemoryException instead.

@shargon
Copy link
Member Author

shargon commented Feb 28, 2022

I have implemented another solution (process value in a DFS fashion and restrict the size the real result to MaxOracleResponseSize)

Could you share your solution?

@fyrchik
Copy link
Contributor

fyrchik commented Feb 28, 2022

Could you share your solution?

Here it is nspcc-dev/neo-go@e4ec405 .

The idea is that we first parse the filter itself into some struct, which allows us to perform DFS on the value we apply filter on.
Good things about it:

  1. JSON can be formed simultaneously with traversal, avoiding need to construct objects in memory (haven't finished this yet, because of purely technical difficulties).
  2. It can return the correct answer in more cases.

However, there is a single bad thing which makes me like your solution more: my implementation leaves room for filters which take long time to process. And this can't be reflected in GAS fee. I can't come up with an example immediately, but it could exist in theory.

@Jim8y
Copy link
Contributor

Jim8y commented Feb 28, 2022

@fyrchik love your solution, may you please share with us the time it costs to run this oom case when you fully implement it in dfs?

@Jim8y
Copy link
Contributor

Jim8y commented Mar 1, 2022

@shargon hey shargon, do you have any plan to further optimise the jsonpath filter? JsonPath should not process such an empty or simple json at all in this oom case, maybe we can have a preprocess or something to verify both the json and filter before executing it.

@fyrchik
Copy link
Contributor

fyrchik commented Mar 1, 2022

@Liaojinghui the remaining part won't change anything significantly, sadly I also don't have time to work on finishing this. However, some results can already be seen. I took the example from this issue, replaced the last [0,0... with [1,1... and benchmarked againt different depth parameters.

depth  time
4      22.766441ms
5      1.142487091s
6      1m18.027576585s

So, 1 minute execution time is certainly not what we want.

@shargon
Copy link
Member Author

shargon commented Mar 3, 2022

So, 1 minute execution time is certainly not what we want.

We can reduce the max filter length, and the max depth to 4.

@fyrchik
Copy link
Contributor

fyrchik commented Mar 4, 2022

@shargon but should we? In your solution it is easy to see why the time is bounded: we move through the filter in one direction,
maintain the list of current objects and don't allow this list to grow large. IMO limiting max depth can prevent some reasonable applications from working, while limiting intermediate objects amount achieve exactly what we want: do not process large or time-consuming inputs.

@Jim8y
Copy link
Contributor

Jim8y commented Mar 4, 2022

@fyrchik how do we deal with a reasonable application but will cause oom? No matter how reasonable it is, the core problem here is we do not have the ability or enough memory to process it.

And size limit can be seen everywhere in the virtual machine.

@fyrchik
Copy link
Contributor

fyrchik commented Mar 9, 2022

@Liaojinghui I think we agree on the issue. The choice here is between restricting the amount of intermediate objects and further restricting maximum allowed depth. They serve the same purpose, so I see less value in restricting both of these parameters.
But with the former (this PR) we will be able to support a wider class of reasonable applications.

@shargon shargon merged commit 24389c6 into neo-project:develop Mar 17, 2022
@shargon shargon deleted the fix-oom branch March 17, 2022 09:12
@superboyiii superboyiii mentioned this pull request Mar 18, 2022
18 tasks
shargon added a commit that referenced this pull request Apr 6, 2022
* Add ToJson overload (#2671)

* Add ToJson overload

* change

* Update src/neo/VM/Helper.cs

* Update src/neo/VM/Helper.cs

* Update src/neo/VM/Helper.cs

* Update src/neo/VM/Helper.cs

Co-authored-by: Jinghui Liao <jinghui@wayne.edu>

* Update src/neo/VM/Helper.cs

Co-authored-by: Jinghui Liao <jinghui@wayne.edu>

Co-authored-by: Shargon <shargon@gmail.com>
Co-authored-by: Jinghui Liao <jinghui@wayne.edu>

* Fix oom (#2665)

* Fix oom

* Revert reorder

* parameters order

Co-authored-by: Erik Zhang <erik@neo.org>

* Optimize inventory (#2659)

* add `murmur32` to crypto lib (#2604)

* 3.2.0

* fix

Co-authored-by: Shargon <shargon@gmail.com>
Co-authored-by: Jinghui Liao <jinghui@wayne.edu>
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.

5 participants