Node.js has a concept of a “Node.js instance”, that is commonly being referred to as node::Environment. Each node::Environment is associated with:

    • Exactly one v8::Isolate, i.e. one JS Engine instance,
    • Exactly one uv_loop_t, i.e. one event loop, and
    • A number of v8::Contexts, but exactly one main v8::Context.
    • One node::IsolateData instance that contains information that could be shared by multiple node::Environments that use the same v8::Isolate. Currently, no testing if performed for this scenario.

    In order to set up a v8::Isolate, an v8::ArrayBuffer::Allocator needs to be provided. One possible choice is the default Node.js allocator, which can be created through node::ArrayBufferAllocator::Create(). Using the Node.js allocator allows minor performance optimizations when addons use the Node.js C++ Buffer API, and is required in order to track ArrayBuffer memory in [process.memoryUsage()][].

    Additionally, each v8::Isolate that is used for a Node.js instance needs to be registered and unregistered with the MultiIsolatePlatform instance, if one is being used, in order for the platform to know which event loop to use for tasks scheduled by the v8::Isolate.

    The node::NewIsolate() helper function creates a v8::Isolate, sets it up with some Node.js-specific hooks (e.g. the Node.js error handler), and registers it with the platform automatically.

    1. int RunNodeInstance(MultiIsolatePlatform* platform,
    2. const std::vector<std::string>& args,
    3. const std::vector<std::string>& exec_args) {
    4. int exit_code = 0;
    5. // Set up a libuv event loop.
    6. uv_loop_t loop;
    7. int ret = uv_loop_init(&loop);
    8. if (ret != 0) {
    9. fprintf(stderr, "%s: Failed to initialize loop: %s\n",
    10. args[0].c_str(),
    11. uv_err_name(ret));
    12. return 1;
    13. }
    14. std::shared_ptr<ArrayBufferAllocator> allocator =
    15. ArrayBufferAllocator::Create();
    16. Isolate* isolate = NewIsolate(allocator, &loop, platform);
    17. if (isolate == nullptr) {
    18. fprintf(stderr, "%s: Failed to initialize V8 Isolate\n", args[0].c_str());
    19. return 1;
    20. }
    21. {
    22. Locker locker(isolate);
    23. Isolate::Scope isolate_scope(isolate);
    24. // Create a node::IsolateData instance that will later be released using
    25. // node::FreeIsolateData().
    26. std::unique_ptr<IsolateData, decltype(&node::FreeIsolateData)> isolate_data(
    27. node::CreateIsolateData(isolate, &loop, platform, allocator.get()),
    28. node::FreeIsolateData);
    29. // Set up a new v8::Context.
    30. HandleScope handle_scope(isolate);
    31. Local<Context> context = node::NewContext(isolate);
    32. if (context.IsEmpty()) {
    33. fprintf(stderr, "%s: Failed to initialize V8 Context\n", args[0].c_str());
    34. return 1;
    35. }
    36. // The v8::Context needs to be entered when node::CreateEnvironment() and
    37. // node::LoadEnvironment() are being called.
    38. Context::Scope context_scope(context);
    39. // Create a node::Environment instance that will later be released using
    40. // node::FreeEnvironment().
    41. std::unique_ptr<Environment, decltype(&node::FreeEnvironment)> env(
    42. node::CreateEnvironment(isolate_data.get(), context, args, exec_args),
    43. node::FreeEnvironment);
    44. // Set up the Node.js instance for execution, and run code inside of it.
    45. // There is also a variant that takes a callback and provides it with
    46. // the `require` and `process` objects, so that it can manually compile
    47. // and run scripts as needed.
    48. // The `require` function inside this script does *not* access the file
    49. // system, and can only load built-in Node.js modules.
    50. // `module.createRequire()` is being used to create one that is able to
    51. // load files from the disk, and uses the standard CommonJS file loader
    52. // instead of the internal-only `require` function.
    53. MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(
    54. env.get(),
    55. "const publicRequire ="
    56. " require('module').createRequire(process.cwd() + '/');"
    57. "globalThis.require = publicRequire;"
    58. "require('vm').runInThisContext(process.argv[1]);");
    59. if (loadenv_ret.IsEmpty()) // There has been a JS exception.
    60. return 1;
    61. {
    62. // SealHandleScope protects against handle leaks from callbacks.
    63. SealHandleScope seal(isolate);
    64. bool more;
    65. do {
    66. uv_run(&loop, UV_RUN_DEFAULT);
    67. // V8 tasks on background threads may end up scheduling new tasks in the
    68. // foreground, which in turn can keep the event loop going. For example,
    69. // WebAssembly.compile() may do so.
    70. platform->DrainTasks(isolate);
    71. // If there are new tasks, continue.
    72. more = uv_loop_alive(&loop);
    73. if (more) continue;
    74. // node::EmitBeforeExit() is used to emit the 'beforeExit' event on
    75. // the `process` object.
    76. node::EmitBeforeExit(env.get());
    77. // 'beforeExit' can also schedule new work that keeps the event loop
    78. // running.
    79. more = uv_loop_alive(&loop);
    80. } while (more == true);
    81. }
    82. // node::EmitExit() returns the current exit code.
    83. exit_code = node::EmitExit(env.get());
    84. // node::Stop() can be used to explicitly stop the event loop and keep
    85. // further JavaScript from running. It can be called from any thread,
    86. // and will act like worker.terminate() if called from another thread.
    87. node::Stop(env.get());
    88. }
    89. // Unregister the Isolate with the platform and add a listener that is called
    90. // when the Platform is done cleaning up any state it had associated with
    91. // the Isolate.
    92. bool platform_finished = false;
    93. platform->AddIsolateFinishedCallback(isolate, [](void* data) {
    94. *static_cast<bool*>(data) = true;
    95. }, &platform_finished);
    96. platform->UnregisterIsolate(isolate);
    97. isolate->Dispose();
    98. // Wait until the platform has cleaned up all relevant resources.
    99. while (!platform_finished)
    100. uv_run(&loop, UV_RUN_ONCE);
    101. int err = uv_loop_close(&loop);
    102. assert(err == 0);
    103. return exit_code;
    104. }