Skip to content

Setting max mem size for v8 instance? #18

Closed

Description

@seanmakesgames

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

Activity

  1. SamSaffron commented on Jun 8, 2016

    @SamSaffron
    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. 
    
  2. seanmakesgames commented on Jun 8, 2016

    @seanmakesgames
    ContributorAuthor

    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

  3. seanmakesgames commented on Jun 30, 2016

    @seanmakesgames
    ContributorAuthor

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

  4. wk8 commented on Jun 30, 2016

    @wk8
    Contributor

    @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

  5. wk8 commented on Jun 30, 2016

    @wk8
    Contributor
  6. seanmakesgames commented on Jun 30, 2016

    @seanmakesgames
    ContributorAuthor

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

  7. seanmakesgames commented on Jul 1, 2016

    @seanmakesgames
    ContributorAuthor
        # 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)

  8. seanmakesgames commented on Aug 5, 2016

    @seanmakesgames
    ContributorAuthor

    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

  9. seanmakesgames commented on Aug 5, 2016

    @seanmakesgames
    ContributorAuthor

    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.

  10. seanmakesgames commented on Aug 8, 2016

    @seanmakesgames
    ContributorAuthor

    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. 🚢

  11. seanmakesgames commented on Aug 25, 2016

    @seanmakesgames
    ContributorAuthor

    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.

  12. seanmakesgames commented on Dec 16, 2016

    @seanmakesgames
    ContributorAuthor

    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

  13. seanmakesgames commented on Mar 7, 2017

    @seanmakesgames
    ContributorAuthor

    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!

  14. SamSaffron commented on Mar 7, 2017

    @SamSaffron
    Collaborator
  15. added a commit that references this issue on May 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions