Open-Source Wikis

/

Node.js

/

Primitives

/

Environment and Realm

nodejs/node

Environment and Realm

Three C++ types describe the running shape of a Node process: IsolateData, Environment, and Realm. Anywhere in src/, the local Environment* (or Realm*) is the gateway to almost every other subsystem.

IsolateData

IsolateData (declared in src/env.h) holds shared, per-v8::Isolate state. Some of it cannot reasonably live on the Environment: V8 string symbols, snapshot helpers, the platform pointer.

Member Holds
node::ArrayBufferAllocator* Allocator used for ArrayBuffers in this isolate.
Per-isolate snapshot data Used to bootstrap the principal Realm from the embedded snapshot.
Cached V8 strings and symbols Like "fd", "length", "data", the symbol names V8 needs.
worker::WorkerThreadsPerIsolateData Pool registry for sibling workers if any.

There is exactly one IsolateData per v8::Isolate. Workers each have their own isolate and thus their own IsolateData.

Environment

Environment (src/env.h, src/env.cc, ~86K LOC for env.cc and ~45K for env.h) is the central per-thread context.

graph TD
    Iso[v8::Isolate] --> IsoData[IsolateData]
    IsoData --> Env[Environment]
    Env --> Loop[uv_loop_t]
    Env --> Opts[per-process + per-Environment Options]
    Env --> Insp[InspectorAgent]
    Env --> Perf[PerformanceState + histograms]
    Env --> Perm[Permission registry]
    Env --> Cleanup[CleanupQueue]
    Env --> Realms[principal Realm + ShadowRealms]
    Env --> Misc[trace state, async-context-frame, source maps, etc.]

Things that live on Environment:

  • The libuv loop the thread runs.
  • The Options (CLI flags) for this Environment, plus the per-process EnvironmentOptions.
  • The inspector Agent.
  • Performance state, histograms, the eventloop-delay histogram.
  • The Permission registry (see Permission Model).
  • Cleanup hooks: anything that needs to tear down before the loop closes registers a RegisterCleanupHook.
  • The active Realms (one principal Realm plus zero or more ShadowRealms).
  • The list of BaseObjects currently alive that pin the loop alive.
  • Trace state, source-map cache pointer, async-context-frame state.

Lifecycle

sequenceDiagram
    participant Inst as NodeMainInstance
    participant Env
    participant Realm
    participant Loop as libuv

    Inst->>Env: Environment::Create
    Env->>Realm: PrincipalRealm::CreateMainRealm
    Inst->>Env: LoadEnvironment(env, ...)
    Env->>Loop: uv_run
    Loop-->>Env: callbacks via Realm
    Loop->>Env: empty -> exit
    Inst->>Env: Environment::Stop, Cleanup

Environment::CleanupHandles waits for libuv handles to close. Environment::RunCleanup walks the cleanup queue. Environment::FreeEnvironment deletes the Environment and its Realms.

Realm

Realm (src/node_realm.h, src/node_realm.cc) is the per-V8-context state. The "principal Realm" is created automatically when an Environment is built; ShadowRealms (Stage-3 TC39) and worker threads have their own Realms too.

What lives on a Realm:

  • The V8 Context.
  • The JS-visible process object.
  • The internalBinding(name) namespace for this Realm.
  • Per-Realm BindingData structs (see BaseObject and BindingData).
  • The BuiltinModule cache for require('node:...').
  • Bookkeeping for process.moduleLoadList.

Bootstrap

Realm::BootstrapRealm() is the one routine that turns a fresh V8 context into a Node-shaped Realm:

  1. Instantiate per-context primordials by running lib/internal/per_context/primordials.js.
  2. Instantiate DOMException and MessagePort JS shims (lib/internal/per_context/domexception.js, messageport.js).
  3. Compile and run lib/internal/bootstrap/realm.js to install internalBinding, BuiltinModule, process.binding.
  4. For the principal Realm, run lib/internal/bootstrap/node.js (the public bootstrap).
  5. For ShadowRealms, run lib/internal/bootstrap/shadow_realm.js.

When the embedded snapshot is in use, all of these steps are skipped — the V8 heap is restored from the binary's snapshot blob.

Picking the right type for new code

  • If your code is per-thread and per-event-loop, store it on Environment.
  • If your code is per-V8-context (per-Realm), store it on Realm.
  • If your code is shared across isolates / not Node-specific, it belongs on IsolateData or as a free function.

The relevant macros are IsolateData::PER_ISOLATE_* (for isolate-wide caches), Environment::Field accessors, and Realm::PER_REALM_* for per-realm storage.

Integration points

  • Snapshots (see Builtins and primordials) operate on Realm, persisting the V8 heap and walking BaseObjects reachable from it.
  • Cleanup is driven by Environment::CleanupHooks. Subsystems register from their Initialize function (Environment::AtExit).
  • Per-binding storage lives on the Realm via BindingData.

Entry points for modification

  • Adding per-Environment state? Extend Environment::Field enum + inspector.h-style accessor (or use Environment::set_xxx() style declared in src/env.h).
  • Adding per-Realm state? Use Realm::AddBindingData<T>() to register a BindingData subclass.
  • Cleanup ordering matters: register a cleanup hook with Environment::AtExit(cb) so it runs in the Environment teardown sequence.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Environment and Realm – Node.js wiki | Factory