Skip to content

Instantly share code, notes, and snippets.

@rxgpt
Last active December 30, 2019 21:14
Show Gist options
  • Select an option

  • Save rxgpt/54d038d37fe3b6b1bc830280aad01c30 to your computer and use it in GitHub Desktop.

Select an option

Save rxgpt/54d038d37fe3b6b1bc830280aad01c30 to your computer and use it in GitHub Desktop.
The inner workings of Node.js

The inner workings of Node.js

Overview

timers.js: timers

Timers

  • A JS hashmap of linked lists of timers, keyed on the timer offset in ms. Timers are added FIFO.
  • In libuv, each linked list gets a timer handle.
  • The timer handle's callback will call a JS callback, listOnTimeout(), which fires all the due timers in the linked list corresponding to the handle, then calculates the new timeout time for the corresponding handle.

setImmediate()

  • A JS queue of immediate callbacks & a processImmediate() function that calls all the immediate callbacks.
  • A check handle, immediate_check_handle, belonging to the process & created when the process object is initialized.
  • A boolean in the process object that toggles using the immediate handle, with a setter callback that toggles the active state of the check handle.
  • When the check handle is made active, its C++ callback is checkImmediate(), which in turn makes a callback to the JS function processImmediate().

fs.js: file system

libuv fs methods: uv_fs_open, uv_fs_close, etc. These take as input a callback. If the callback is NULL, they will execute synchronously in the libuv threadpool.

ASYNC_CALL & SYNC_CALL in node_file.cc: these will create a uv_fs_t and call the respective uv_fs_ methods on it, with or without a callback function.

C++ methods in node_file.cc:, Open, FStat, Read, Close.

JS methods in fs.js: readFile, readFileSync, etc. These will call the C++ methods. The async methods pass in a callback, which gets passed down to the libuv calls.

Reading a file:

  • Open: returns a file descriptor (integer that represents this opened file entry from the kernel)
  • FStat: populates an array with file information corresponding to the file descriptor
  • CreateBuffer
  • ReadSync: populates buffer
  • Close: closes file descriptor

events.js: EventEmitter

Maintains an array of arrays of listener functions, keyed by event name. (The 2nd array is only created if needed, i.e., if there is more than 1 listener for a particular event. Otherwise the listener is stored in the top-level array.)

on(type, listener): really _addListener(). Adds the listener to the top-level array if it's the first listener for that event, otherwise add it to the end of the array of listeners for that type.

emit(type, arguments): Calls each of the listeners registered to that event. Listener calls are achieved via optimized EmitNone, EmitOne, EmitTwo, EmitThree, and EmitMany methods.

Creating an HTTP server

_http_server.js/createServer(requestListener)

  • requestListener is a function which takes as input a request and a response.
  • Creates a new server, registers the requestListener to the "request" event, and registers an internal connectionListener to the "connection" event.

_http_server.js/connectionListener(socket)

  • Set up the socket, allocate & re-initialize a new HTTPParser, establish a 1:1 relationship between socket & parser.
  • Reinitialize() is a C++ function that calls http_parser_init() in deps/http_parser/http_parser.cc. It will init a new HTTPPARSER resource.
  • Set parser.onIncoming() to be the parserOnIncoming() function.

_http_server.js/parserOnIncoming(server, socket, state, req, keepAlive)

  • Called after headers are read on a new message.
  • Creates a response object. Emits the "request" event and sends in the request & response object as arguments.

Listening on a port

server.listen(port, callback) -> net.js/Server.prototype.listen() -> net.js/listenInCluster(server, address, port, addressType, backlog, fd, exclusive) -> net.js/setupListenHandle(address, port, addressType, backlog, fd)

net.js/setupListenHandle(address, port, addressType, backlog, fd) Calls createServerHandle to get a TCPWrap handle, sets its .onconnection property to the onconnection function, then associates the handle with the Server object.

net.js/createServerHandle(address, port, addressType, fd) Creates a new TCPWrap from tcp_wrap.cc and binds it to the address & port supplied.

src/tcp_wrap.cc/bind(address, port) Calls uv_tcp_bind to bind the handle associated with the TCPWrap to the address & port supplied.

onconnection(err, clientHandle) Creates a new JS Socket, sets its server to be the JS Server that is associated with the handle whose onconnection function was called, & then emits a "connection" event on the Server.

Receiving an HTTP connection

In _http_common.js, the function parserOnHeadersComplete will call onIncoming() for a given HTTP parser. The callback in C++ HTTP Parser in node_http_parser.cc will call the JS function parserOnHeadersComplete(). On new connection

Express module

Uses http.CreateServer with the application object as the request listener. The application object is a "function" which contains the route handler logic.

createApplication() creates a new app of type function(req, res, next), then calls app.init(). The function calls app.handle(req, res, next), which in turn calls router.handle(req, res, next).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment