ClearScript: Hard crash if out of memory

First, thank you for this project. We use it extensively to do server-side rendering of our React application and it works really well.

One issue we have encountered (which I don’t if it is possible to fix) is that when we had a memory leak, we would eventually get a hard process crash with this call stack:

Application: w3wp.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.AccessViolationException
   at <Module>.std._Func_class<void>.()()(std._Func_class<void>*)
   at Microsoft.ClearScript.V8.NativeCallbackImpl.Invoke()
   at Microsoft.ClearScript.Util.MiscHelpers.Try(System.Action)
   at Microsoft.ClearScript.Util.MiscHelpers+<>c__DisplayClass23_0.<QueueNativeCallback>b__0(System.Object)
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()

The error can be triggered by this code:

var runtime = new V8Runtime();
var engine = runtime.CreateScriptEngine(V8ScriptEngineFlags.DisableGlobalMembers);
engine.AllowReflection = false;
engine.EnableAutoHostVariables = false;
engine.UseReflectionBindFallback = false;
engine.DefaultAccess = ScriptAccess.None;
engine.Execute("var leak = [];");
for (;;)
{
    engine.Execute("(function() {const x = []; for (let i = 0; i < 10000; i++) x[i] = Math.random(); leak.push(x);})();");
}

I am aware that my code needs to be fixed and that a crash is inevitable, but I would prefer if the crash happened in some other way than an access violation (which I guess is due to an allocation return value not being checked somewhere) that tears down my entire process without the possibility of preventing it.

Preferrably I would eventually get an exception in the Execute() call (which could or could not require that the engine be disposed of afterwards).

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 23

Most upvoted comments

Hi @erik-kallen,

If I read the docs (v8 + clearscript) correctly, it seems that TotalHeapSize will never be decreased, is that correct?

That’s not correct. V8 releases unused committed memory during exhaustive garbage collection. Here’s how you can verify that:

Console.WriteLine(engine.GetRuntimeHeapInfo().TotalHeapSize);
engine.Execute("var x = []; for (var i = 0; i < 50000000; ++i) x.push(i);");
Console.WriteLine(engine.GetRuntimeHeapInfo().TotalHeapSize);
engine.Execute("x = undefined;");
engine.CollectGarbage(true);
Console.WriteLine(engine.GetRuntimeHeapInfo().TotalHeapSize);

I noticed that in your example. you used MaxOldSpaceSize in the V8Constraints to set the limit to a value higher than the MaxHeapSize

Yes. Setting MaxOldSpaceSize significantly higher than MaxHeapSize is critical, as it makes monitoring much more likely to terminate script execution before V8 kills the process.

do you have any recommendations for MaxNewHeapSize [sic]?

No. That doesn’t seem to have much effect in recent V8 versions, whereas careful setting of MaxOldSpaceSize and Max[Runtime]HeapSize can prevent most hard crashes.

BTW, do we get a notification somehow so it would be possible to add something to the event log in the case of OOM?

V8 does allow the host to set up an OOM callback (for logging purposes only; it’ll kill the process as soon as the callback returns), but ClearScript doesn’t expose this. Of course, if you use the techniques discussed here, you can catch OOM before it kills the process and log anything you wish.

Cheers!