DEV Community

Cover image for Crossing the JS/C++ Boundary
Deepal Jayasekara
Deepal Jayasekara

Posted on • Originally published at blog.insiderattack.net

Crossing the JS/C++ Boundary

This article was originally posted on Medium as an extension to my article series on NodeJS Event Loop on Medium:

In NodeJS Event Loop article series, I explained,

In this article, I’m going to explain how NodeJS internally glue a bunch of C/C++ and JavaScript pieces all together to build an amazing server-side Javascript framework.

For you to clearly understand how it works, I have included few code snippets in this post from the NodeJS codebase. Please note that I have truncated certain parts of those for brevity and have added some comments and log lines myself. Also, I would like you to understand that NodeJS codebase is very active and is subject to frequent code refactoring. Therefore, these code snippets might be drastically different across revisions. However, the overall functionality will be almost similar. Although these snippets would be gone in future releases, understanding how they work right now could make it really easy for you to understand the entire NodeJS codebase.

In the Event Loop series, I explained to you how it works, different phases of it and the sequence. But the event loop is implemented in libuv and nowhere in its code is mentioned about process.nextTick. Now the weird question is….Where is process.nextTick callbacks are called? The only thing we know is that they are called at the start and between each phase of the event loop.

I presume you have a sound understanding of the different phases of the event loop, microtasks, process.nextTick and other terminology related to Node. If you haven’t I do appreciate if you can read the aforementioned Event Loop article series before reading this article as you might feel it difficult to follow some of the concepts mentioned here.

First of all, let’s see how process.nextTick is defined in JavaScript context so that we can call it. To come to that, let’s see how NodeJS starts up.

Initialization: Starting V8 Environment

During the initialization of NodeJS runtime, an instance of V8 environment is created and the environment is started by calling Environment::Start method. In this method, SetupProcessObject is executed which makes some interesting C++ functions accessible from JavaScript.

As you can see _setupNextTick function is set in the process object during the initialization of V8 environment. _setupNextTick is a reference to the SetupNextTick function in C++ context. Following is the definition of the SetupNextTick function.

I will explain how this SetupNextTick function comes into play in a while.

Loading Bootstrappers

After initializing V8 environment, two JS scripts called bootstrappers are run.

They are,

Loaders Bootstrapper: internal/bootstrap/loaders.js

Node Bootstrapper: internal/bootstrap/node.js

Node bootstrapper calls its startup function during execution.

During the execution of startup() function, NodeJS requires the next tick module from internal/process/next_tick and executes its setup() export function.

This setup() function is exported from next_tick.js and is a reference to the function setupNextTick defined in the same file.

  • Upon calling, setupNextTick function sets the attribute nextTick in the process object (line 22) as a reference to the nextTick function defined in the same scope. This is how process.nextTick can be called from userland.
  • nextTick function (line 37) merely adds a given callback into a queue.
  • There’s another function called _tickCallback (line 27) defined in the same scope which is where the nextTick queue is processed. For ease of reading, I extracted it out as a separate gist as follows. Let’s read it carefully.
  1. Once _tickCallback() function is called, it will iterate through the queue where nextTick callbacks are queued and will execute each and every callback until there are no callbacks left in the queue (line 4, inner while loop).
  2. Then, the _tickCallback() function will call runMicrotasks() function (line 21). This function will process the microtasks queue (e.g, callbacks of resolved/rejected promises). It’s possible that new nextTick callbacks are added while executing microtasks (e.g, call of process.nextTick in promise resolve/reject callback).
  3. Above step1 and step2 is repeated until no more callbacks left in the nextTick queue (line 3, outer do-while loop)

The golden point is….. You need to trigger **_tickCallback** JavaScript function somehow during two phases of the event loop in order to process the **nextTick** queue and the microtasks queue.

To do this, _tickCallback function should be passed somehow to C++ context.

Binding JS function to C++

_tickCallback function is referenced in C++ context by calling process._setupNextTick inside setupNextTick. Upon the execution of process._setupNextTick, it’s passed tickCallback as the only parameter (see the next_tick.js gist above).

Now if you remember I explain before, process._setupNextTick is actually a C++ function which is referred in JavaScript context upon the initialization of V8 environment. For clarity, I’ll just copy/paste the gist again (It’s time to scroll up if you don’t remember ;))

The equivalent C++ function to process._setupNextTick JS function is SetupNextTick which is defined in node.cc.

This method will call set_tick_callback_function with the first parameter provided. Therefore, whatever you pass as the first parameter to process._setupNextTick is passed to set_tick_callback_function. Now go up and check what we call process._setupNextTick within setupNextTick JS definition.

Wow!! Eventually, the reference to _tickCallback function is passed to set_tick_callback_function in C++. set_tick_callback_function will set the reference to the _tickCallback function as tick_callback_function in the V8 environment. The conclusion is, calling tick_callback_function in V8 environment triggers the execution of JavaScript _tickCallback function which subsequently processes the nextTick queue and the microtask queue.

Now if you recall what I mentioned above….

The golden point is….. You need to trigger **_tickCallback** JavaScript function somehow during two phases of the event loop in order to process the **nextTick** queue and the microtasks queue.

You know how ;)

Now we need to know where tick_callback_function is called in C++ context. Let’s go back to the event loop now.

Crossing the boundary

In NodeJS, we write all our code in Javascript, which means all of our callbacks are JavaScript. So how are they triggered by C? The answer is, this binding glue is the C++ bindings for libuv. Those are the functions written in C++ who bridges C++ and JavaScript and invoke JavaScript functions when libuv asks them to. Let’s try to understand it in a clear way by an example.

When you are adding multiple timers using setTimeout, they all will be grouped together by their timeout value so that timers with the same timeout value will be in a single list. And Javascript will provide a callback function processTimers with each list to execute its callbacks. This callback function will be passed from JavaScript to C++ as OnTimeout in C++, and then C++ to libuv(C) as timer_cb attached to a handle which is invoked by libuv. Essentially, calling the timer_cb of a handle in libuv will trigger the execution of the multiple timers which were set at the same time with the same timeout value (I know you have questions now, but just keep them in mind for my next post on NodeJS timers in detail ;))

How a processTimers() JavaScript function is triggered from libuv

When OnTimeout function in C++ is called, NodeJS will cross the C++ boundary all the way to JavaScript and will execute the given JavaScript callback. When it crosses the C++/JS Boundary, an interesting function is called named MakeCallback.

Now if you search for MakeCallback, you will find multiple overloaded MakeCallback functions in node.cc and async_wrap.cc with different signatures:

At the time of this writing:

Each of them is written for very distinct purpose but if you look closely, you’ll see that they all eventually calls the InternalMakeCallback function defined in node.cc.

When MakeCallback is called, it will pass the appropriate JS callback which needs to be called by the InternalMakeCallback as the 3rd parameter (callback) of the InternalMakeCallback.

This **callback** is the almighty who processes a single phase of the event loop.

Then comes the most important part. You’ll see that after calling the provided callback, scope.Close() function is called.

Let’s look at the definition of scope.Close() function defined in node.cc.

Within this function, it executes the tick_callback_function in V8 environment.

Do you remember what tick_callback_function is? It’s the same _tickCallback JavaScript function which is referred in the C++ context which indicates that every time InternalMakeCallback is called from C++ _tickCallback function is called at the end. Therefore, at the end of each phase, nextTick queue and the microtask queue are processed.

But, If you read my Event Loop article series or if you have personally experienced process.nextTick callbacks and the microtasks are run even before the timers phase start. How is this happen?

This is because the Node Bootstrapper which I mentioned above also loads the CommonJS module loader internally. During the bootstrap of CommonJS module loader, it manually triggers _tickCallback to process any nextTick callbacks added at the start.

PS: For the above examples, I referred to the NodeJS source at revision b267d2aae6. Due to many refactoring processes, the above example codes might be different across different NodeJS versions.

I suppose you now know how the mysterious process.nextTick works. If you have any questions regarding anything I’ve mentioned or if you like to add something please feel free to comment. Thanks.

Background Image Courtesy: https://www.hdwallpapers.in/walls/earth_horizon-wide.jpg

Top comments (0)