The process object
By now it should be clear as to how Node is structured, in terms of V8, the event loop, and so forth. We are now going to discuss, in detail, how instructions that you write (a JavaScript program) are compiled by V8 into a list of instructions whose execution context is accessible via the native Node process
object.
The single thread forming the spine of Node's event loop is V8's event loop. When I/O operations are initiated within this loop they are delegated to libuv, which manages the request using its own (multi-threaded, asynchronous) environment. libuv announces the completion of I/O operations, allowing any callbacks waiting on this event to be re-introduced to the main V8 thread for execution:
Node's
process
object provides information on and control over the current running process. It is an instance of EventEmitter
, is accessible from any scope, and exposes very useful low-level pointers. Consider the following program:
var size = process.argv[2]; var totl = process.argv[3] || 100; var buff = []; for(var i=0; i < totl; i++) { buff.push(new Buffer(size)); process.stdout.write(process.memoryUsage().heapTotal + "\n"); }
Tip
Downloading the example code:
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
Assuming the program file is named process.js
, it would be executed like so:
> node process.js 1000000 100
This execution context first fetches the two command-line arguments via process.argv
, builds a looping construct that grows memory usage depending on these arguments, and emits memory usage data as each new allocation is made. The program sends output to stdout
, but could alternatively stream output to other processes, or even a file:
> node process.js 1000000 100 > out.file
In fact, the familiar console.log
is implemented in the Node core as a wrapper around process.stdout.write
:
console.log = function (d) { process.stdout.write(d + '\n'); };
A Node process begins by constructing a single execution stack, with the global context forming the base of the stack. Functions on this stack execute within their own, local, context (sometimes referred to as scope), which remains enclosed within the global context (which you'll hear referred to as closure). Because Node is evented, any given execution context can commit the running thread to handling an eventual execution context. This is the purpose of callback functions.
Consider the following schematic of a simple interface for accessing the filesystem:
If we were to instantiate Filesystem
and call readDir
a nested execution context structure would be created: (global (fileSystem (readDir (anonymous function) ) ) ). The concomitant execution stack is introduced to Node's single process thread. This stack remains in memory until libuv reports that fs.readdir
has completed, at which point the registered anonymous callback fires, resolving the sole pending execution context. As no further events are pending, and the maintenance of closures no longer necessary, the entire structure can be safely torn down (in reverse, beginning with anonymous), and the process can exit, freeing any allocated memory. This method of building up and tearing down a single stack is what Node's event loop is ultimately doing.
We'll explore the full suite of commands and attributes contained by the process
object as we continue to develop examples and libraries in this book.