node.jsv8qt5sehdebugbreak

How to handle V8 engine crash when process runs out of memory


Both node console and Qt5's V8-based QJSEngine can be crashed by the following code:

a = []; for (;;) { a.push("hello"); }

node's output before crash:

FATAL ERROR: JS Allocation failed - process out of memory

QJSEngine's output before crash:

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

If I run my QJSEngine test app (see below) under a debugger, it shows a v8::internal::OS::DebugBreak call inside V8 code. If I wrap the code calling QJSEngine::evaluate into __try-__except (SEH), then the app won't crash, but this solution is Windows-specific.

Question: Is there a way to handle v8::internal::OS::DebugBreak in a platform-independent way in node and Qt applications?

=== QJSEngine test code ===

Development environment: QtCreator with Qt5 and Windows SDK 7.1, on Windows XP SP3

QJSEngineTest.pro:

TEMPLATE = app
QT -= gui
QT += core qml
CONFIG -= app_bundle
CONFIG += console
SOURCES += main.cpp
TARGET = QJSEngineTest

main.cpp without SEH (this will crash):

#include <QtQml/QJSEngine>

int main(int, char**)
{
  try {
    QJSEngine engine;
    QJSValue value = engine.evaluate("a = []; for (;;) { a.push('hello'); }");
    qDebug(value.isError() ? "Error" : value.toString().toStdString().c_str());
  } catch (...) {
    qDebug("Exception");
  }
  return 0;
}

main.cpp with SEH (this won't crash, outputs "Fatal exception"):

#include <QtQml/QJSEngine>
#include <Windows.h>

void runTest()
{
  try {
    QJSEngine engine;
    QJSValue value = engine.evaluate("a = []; for (;;) { a.push('hello'); }");
    qDebug(value.isError() ? "Error" : value.toString().toStdString().c_str());
  } catch (...) {
    qDebug("Exception");
  }
}

int main(int, char**)
{
  __try {
    runTest();
  } __except(EXCEPTION_EXECUTE_HANDLER) {
    qDebug("Fatal exception");
  }
  return 0;
}

Solution

  • I don't believe there's a cross-platform way to trap V8 fatal errors, but even if there were, or if there were some way to trap them on all the platforms you care about, I'm not sure what that would buy you.

    The problem is that V8 uses a global flag that records whether a fatal error has occurred. Once that flag is set, V8 will reject any attempt to create new JavaScript contexts, so there's no point in continuing anyway. Try executing some benign JavaScript code after catching the initial fatal error. If I'm right, you'll get another fatal error right away.

    In my opinion the right thing would be for Node and Qt to configure V8 to not raise fatal errors in the first place. Now that V8 supports isolates and memory constraints, process-killing fatal errors are no longer appropriate. Unfortunately it looks like V8's error handling code does not yet fully support those newer features, and still operates with the assumption that out-of-memory conditions are always unrecoverable.