More on Node VM

So I wanted to understand a bit more about what is going on under the covers with Node VM. To do that, I pulled open the node code itself. To start with, when we do a require(‘vm’) we are referencing the builtin vm module, which is contained in Node’s libs folder under the name ‘vm.js’. The code for it is quite simple, so I’ll past it here:

var binding = process.binding('evals');
 
exports.Script = binding.Script;
exports.createScript = function(code, ctx, name) {
  return new exports.Script(code, ctx, name);
};
 
exports.createContext = binding.Script.createContext;
exports.runInContext = binding.Script.runInContext;
exports.runInThisContext = binding.Script.runInThisContext;
exports.runInNewContext = binding.Script.runInNewContext;

This is from the version I am currently running which is Node 0.4.9.

What we see here is a call to process.binding to access ‘evals’ in the node C++ code. The rest is mostly just mapping logic, giving us the various methods we have already been using by mapping them to the methods in the C++ code. Pretty simple. To understand what is actually happening here though, we have to jump down into the land of C++.

In the src directory for node, in the file node_script.cc, we find the method that does the real work РWrappedScript::EvalMachine. Taking a look at this, we can get a sense of what differs between passing in a context via runInContext vs runInNewContext and runInThisContext.

The first significant time we see a differentiation is here:

  if (context_flag == newContext) {
    // Create the new context
    context = Context::New();
 
  } else if (context_flag == userContext) {
    // Use the passed in context
    Local<Object> contextArg = args[sandbox_index]->ToObject();
    WrappedContext *nContext = ObjectWrap::Unwrap<WrappedContext>(sandbox);
    context = nContext->GetV8Context();
  }

We can see that if we do a runInNewContext, we must create a new context object. On the other hand, if we pass in a context object previously created we instead perform a variety of gyrations to ‘unwrap’ the context and get the V8 context of it.

Later, we also find that disposal is quite different:

  if (context_flag == newContext) {
    // Clean up, clean up, everybody everywhere!
    context->DetachGlobal();
    context->Exit();
    context.Dispose();
  } else if (context_flag == userContext) {
    // Exit the passed in context.
    context->Exit();
  }

It is clear from our performance results that the object generation and subsequent detach/dispose is expensive enough to make a noticeable difference in our run time.

We also find this code which occurs whether or not a user is doing a new context or passing an existing one:

  // New and user context share code. DRY it up.
  if (context_flag == userContext || context_flag == newContext) {
    // Enter the context
    context->Enter();
 
    // Copy everything from the passed in sandbox (either the persistent
    // context for runInContext(), or the sandbox arg to runInNewContext()).
    keys = sandbox->GetPropertyNames();
 
    for (i = 0; i < keys->Length(); i++) {
      Handle<String> key = keys->Get(Integer::New(i))->ToString();
      Handle<Value> value = sandbox->Get(key);
      if (value == sandbox) { value = context->Global(); }
      context->Global()->Set(key, value);
    }
  }

Additionally, there is this set of code which occurs to copy the values back out to the object used from javascript:

  if (context_flag == userContext || context_flag == newContext) {
    // success! copy changes back onto the sandbox object.
    keys = context->Global()->GetPropertyNames();
    for (i = 0; i < keys->Length(); i++) {
      Handle<String> key = keys->Get(Integer::New(i))->ToString();
      Handle<Value> value = context->Global()->Get(key);
      if (value == context->Global()) { value = sandbox; }
      sandbox->Set(key, value);
    }
  }

Looking at all these however, it is important to note that these are if and else if statements – so all of this code (along with a few other tidbits) are ONLY executed if the context is to be new or user provided. There is a third option in the code – which is to say, runInThisContext. None of this code executes in a such a case, which seems consistent with the significant performance difference we see between runInThisContext and the other options.

It is also important to note that when supplying a context, the way values are communicated back and forth is actually via a copy operation – the scripts is not directly editing the object.

This entry was posted in Javascript, Node.js and tagged , , , , . Bookmark the permalink.

3 Responses to More on Node VM

  1. Pingback: More on Node VM | David Clifton's Blog | Javascript Programming | Scoop.it

  2. Pingback: Javascript: Links, News, Resources (2) « Angel “Java” Lopez on Blog

  3. Pingback: Node.js: Links, news, Resources (3) « Angel “Java” Lopez on Blog

Leave a Reply

Your email address will not be published. Required fields are marked *

*