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

Setting max mem size for v8 instance? #18

Closed
seanmakesgames opened this issue Jun 4, 2016 · 14 comments · Fixed by #54
Closed

Setting max mem size for v8 instance? #18

seanmakesgames opened this issue Jun 4, 2016 · 14 comments · Fixed by #54

Comments

@seanmakesgames
Copy link
Contributor

Is there a way currently to set the heap size to a specific amount? Maybe from a config, or on the context object? Found this online for v8:
--max-old-space-size

@SamSaffron
Copy link
Collaborator

I would love to do something, but we need to do a bunch of research here... first place I would like us to look:

Is all the memory V8 is allocating going through https://github.com/discourse/mini_racer/blob/master/ext/mini_racer_extension/mini_racer_extension.cc#L12-L20 ? If it is can we easily add:

ctx = MiniRacer::Context.new
ctx.eval(javascript)
puts ctx.bytes_used 
=>  10002 

If the block allocater does then we can probably simply use http://bespin.cz/~ondras/html/classv8_1_1HeapStatistics.html which is returned via http://bespin.cz/~ondras/html/classv8_1_1Isolate.html#add32e78544edaf8946ed9b328167e5e4

We need to do this first, to implement caps we would have to use: http://bespin.cz/~ondras/html/classv8_1_1Isolate.html#acff413b8633aa13f1308697c0ce8c5fa but that would be fairly tricky, what should we do when you reach the limit? burn the isolate? leave it around and shift it into error state?

I would also like us to add a way dispose of V8 memory if you want to do it by hand:

ctx = MiniRacer::Context.new
ctx.eval(javascript)
ctx.dispose  # will free all underlying c++ assets (same as deallocate) 
ctx.eval(javascript)
=> MiniRacer::ContextDisposedError

and perhaps

MiniRacer::Context.run do |ctx|
   ctx.eval(javascript)
end
# everything is freed except for the 1 Ruby RVALUE. 

@seanmakesgames
Copy link
Contributor Author

That all looks good!

For my specific purposes, (since I'm not in-control of the js code that's being executed by the context) I need to be able to completely halt execution when the memory cap is hit.

So preferably, it would be the memory equivalent of the timeout that's passed in on context create.

Guaranteed my players are writing code that looks like this:

var a = []
for(var i = 0; i < 10000000000000............; i++) a.push(i);

I figure the best bet for implementing something like that based on what you linked above, is to use the allocation callback and call stop when the mem hits the threshold. Probably using some c-side alloc size tracking, so we're not constantly re-querying.

I do like the context block-form and the dealloc stuff- luckily, memory is holding real well between runs :D

@seanmakesgames
Copy link
Contributor Author

I'll put something simple together for just the max-heap setting of this. Hopefully we can put something together that has reasonable performance.

@wk8
Copy link
Contributor

wk8 commented Jun 30, 2016

@seanmakesgames : FWIW, MiniRacer now supports run time flags, so you could try doing something like this:

MiniRacer::Platform.set_flags! max_old_space_size: 20

It seems to work, albeit there still is some work to be done to turn that into a friendly Ruby exception:

irb(main):001:0> MiniRacer::Platform.set_flags! max_old_space_size: 10
=> ["--max_old_space_size 10"]
irb(main):002:0> 
irb(main):003:0* js = <<-JS
irb(main):004:0" var a = []
irb(main):005:0" for(var i = 0; i < 1000000000000000; i++) a.push(i);
irb(main):006:0" JS
=> "var a = []\nfor(var i = 0; i < 1000000000000000; i++) a.push(i);\n"
irb(main):007:0> 
irb(main):008:0* ctx = MiniRacer::Context.new
=> #<MiniRacer::Context:0x007f89549c56f0 @functions={}, @timeout=nil, @current_exception=nil, @isolate=#<MiniRacer::Isolate:0x007f89549c5600 @lock=#<Thread::Mutex:0x007f89549c55d8>>>
irb(main):009:0> 
irb(main):010:0* ctx.eval js

<--- Last few GCs --->

      91 ms: Mark-sweep 7.9 (13.9) -> 5.1 (11.1) MB, 1.1 / 0.0 ms [allocation failure] [GC in old space requested].
      92 ms: Mark-sweep 5.1 (11.1) -> 5.1 (11.1) MB, 1.3 / 0.0 ms [allocation failure] [GC in old space requested].
      93 ms: Mark-sweep 5.1 (11.1) -> 5.1 (10.1) MB, 1.1 / 0.0 ms [last resort gc].
      94 ms: Mark-sweep 5.1 (10.1) -> 5.1 (10.1) MB, 1.1 / 0.0 ms [last resort gc].


<--- JS stacktrace --->

==== JS stack trace =========================================

Security context: 0x1598551c8b11 <JS Object>
    2: /* anonymous */ [0x159855104301 <undefined>:~1] [pc=0xa4a3a443e8c] (this=0x1598551cf629 <JS Global Object>)

==== Details ================================================

[2]: /* anonymous */ [0x159855104301 <undefined>:~1] [pc=0xa4a3a443e8c] (this=0x1598551cf629 <JS Global Object>) {
// optimized frame
--------- s o u r c e   c o d e ---------
var a = []\x0afor(var i = 0; i < 100000000...


#
# Fatal error in CALL_AND_RETRY_LAST
# Allocation failed - process out of memory
#

Seems like we'd want to register a error handler with V8, see https://github.com/v8/v8/blob/5.1.281.59/src/api.cc#L242-L244

@wk8
Copy link
Contributor

wk8 commented Jun 30, 2016

@seanmakesgames
Copy link
Contributor Author

Awesome! Thanks Jean @wk8
I'll see what I can put together. :)

@seanmakesgames
Copy link
Contributor Author

    # MiniRacer::Platform.set_flags! max_old_space_size: 10
    # todo find a way to get the above working in a test case.

    context = MiniRacer::Context.new

    assert_raises(MiniRacer::ScriptTerminatedError) { context.eval('var a = []; while(true) a.push(new Array(100000000));') }

put this into a quick test case, but not sure where to hook up the callback or how to forward the exception safely.

Also not sure how to set the space size flag just for the one test case. (as it's a platform option)

@seanmakesgames
Copy link
Contributor Author

Got back into investigation of this and unfortunately it looks like there's no way to recover from the oom exceptions through the artificially set memory constraints :/

The callback works, but there's no way to signal 'this is ok' -- this used to work, but is now removed:
https://bugs.chromium.org/p/v8/issues/detail?id=2638
https://bugs.chromium.org/p/v8/issues/detail?id=3060
http://stackoverflow.com/questions/16797423/how-to-handle-v8-engine-crash-when-process-runs-out-of-memory

As far as I can tell, these platform limits are not in sync with failstate handling. My plan now is to register an alloc tracker/counter and call terminate like we do for timeouts.
Any thoughts are much appreciated

@seanmakesgames
Copy link
Contributor Author

Proposal for implementation:
Alongside with 'timeout' context creation option, a max_mem option can be provided.
If the max_mem option is provided, the allocation callback will be registered. Allocation callback tracks amount allocated minus amount freed, if an alloc puts the net above max_mem, then TerminateExecution will be called in the same manner as timeout.

Isolate and context are left in the same state as a timeout. Non-fatal failure & further evals will be available.

I do think there might be a perf hit from the alloc callback, but the code impact will be only be applicable to those that provide the max_mem option.

@seanmakesgames
Copy link
Contributor Author

Ok made some progress on this, but hitting up some roadblocks in a few places:

  • multithreading support for this option is going to be hard. There's no 'userdata' passed into the callback & it's not a functor, which would mean some sort of array-of-function-pointers-based system & thread-count limit, etc, etc. > for my purposes, I use process-based concurrency, so I don't need multi-threading here.
  • TerminateExecution is not working from the callback. :\
  • TerminateExecution looks like a timeout termination, need some way to differentiate between the two

In progress-PR here:
#36

Any support is much appreciated. Trying to full-launch in the next month. 🚢

@seanmakesgames
Copy link
Contributor Author

by the way, these are a segfault when they happen. There is no way to recover from the v8 built-in OOM failures, even when an artificial max-memory amount is set with the platform variables.

@seanmakesgames
Copy link
Contributor Author

Looks like the new way to handle this is w/ an interruptrequest & checking the heapsize
http://stackoverflow.com/questions/10285737/limiting-memory-of-v8-context/34695455#34695455

I'm worried about this though:
https://groups.google.com/forum/#!topic/v8-users/kl-Zz7UQls0

@seanmakesgames
Copy link
Contributor Author

Getting a bit more time on this, and looks like there's a tasty OOM handler in v8 5.4 -
Should I open a separate issue for upgrade to 5.4? @SamSaffron
Hope all is well!

@SamSaffron
Copy link
Collaborator

SamSaffron commented Mar 7, 2017 via email

cataphract pushed a commit to cataphract/mini_racer that referenced this issue May 6, 2020
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 a pull request may close this issue.

3 participants