3

I'm reading a Node.js book to try to get a clear idea of how Node.js handles events, asynchronous functions and their callbacks, non-blocking I/O, etc. Following is a brief synopsis of how I understand the event loop basics, then after that, a few questions I wasn't able to find clear answers to in the book. If anything in my basic understanding is wrong, please correct me. Then the questions follow.

As I understand, Node.js runs all functionality on a single thread. This includes the event loop. In other words, when an event occurs, e.g. an HTTP request, that request goes onto the event queue. The event loop itself pulls one event off the queue at a time and processes it in its entirety. This can mean returning a simple result immediately, or in the case of I/O to a third party (like a database or file system) a non-blocking, asynchronous call is made, with a callback to tell the system what to do when the async function is complete. Meanwhile, all other events in the queue are waiting. In case a developer writes a method that doesn't offload work asynchronously or to workers, the entire server will hang while the task is processed.

If any of that was wrong, please clarify, because it may have a bearing on my understanding of the answer to the following questions, which I hope aren't too stupid.

  1. File system, database, and other such calls don't block because Node.js offloads them to asynchronous calls, but if Node.js is single threaded, who is listening for new events and managing the event loop? While Node.js is processing some event, who is listening and managing the queuing of new incoming events so they are not lost while Node.js's attention is elsewhere? Do I fundamentally misunderstand how this works? Is there like a daemon running or something, apart from the part of the engine that is running our code, that manages the event queue?
  2. Similar question for asynchronous functions' callbacks ... who is listening for and managing incoming callbacks while Node.js is already processing some callback or event, so that a callback isn't lost due to its arrival during processing?
  3. When an async function completes, does the callback interrupt the event loop and take immediate control, or does the return of an async function re-enter the event loop as a new event at the end of the queue and wait its own turn again? In other words, is a callback from an async function treated like any other event, or differently? If it is treated differently, how? Is this a fundamentally stupid question?
  4. Can a callback be blocking, the same way as a badly written event handlers (e.g. HTTP request handler) can? In other words, can badly written code contained in a callback block the server? (I assume the answer is yes but want to know if I'm right.)

Here is a similar question, with great answers, but a slightly different nuance. This linked question addresses asynchronicity with regard to user code but one of the answers seems to contain an incidental answer to my question. I'm interested in Node's internal workings that the user never interacts with, as well, e.g. who manages new client request insertion into the event queue while Node is blocking, same for callbacks, etc. Is NodeJS really Single-Threaded?

Community
  • 1
  • 1
Steverino
  • 2,099
  • 6
  • 26
  • 50

2 Answers2

3

First of all, it helps to start with this:

The phrase "Node.js is single threaded" is slightly misleading and technically incorrect. For all intents and purposes within your code, your code is single-threaded, but Node itself is not. There are some API's and operations (generally it's asynchronous I/O that does this) within Node that will go and use a different thread either within the kernel or within LibUV's thread pool.

Now to break this down by order of each question:

  1. File system, database, and other such calls don't block because Node.js offloads them to asynchronous calls, but if Node.js is single threaded, who is listening for new events and managing the event loop? While Node.js is processing some event, who is listening and managing the queuing of new incoming events so they are not lost while Node.js's attention is elsewhere? Do I fundamentally misunderstand how this works? Is there like a daemon running or something, apart from the part of the engine that is running our code, that manages the event queue?

While Node is processing some event, nobody is listening to the queue. They aren't lost because, well, it's a queue. It will wait until Node is done what it is doing, such that it can return to the queue to check it for the next thing to do. This is part of Node's "event loop" and why blocking code (code that never allows Node to return to the top of the event loop, or takes a long time to do so) is problematic.

Asynchronous I/O operations are in separate threads and can deposit things on the queue while Node is doing something else. When Node is done doing that thing, it can go deal with things on the queue.

  1. Similar question for asynchronous functions' callbacks ... who is listening for and managing incoming callbacks while Node.js is already processing some callback or event, so that a callback isn't lost due to its arrival during processing?

Again, it isn't lost because it's a queue. It's a fundamental part of that data structure. The event will wait in the queue until something takes it off of the queue. It will not disappear regardless of whether anything is listening right at that time, it must be consumed.

  1. When an async function completes, does the callback interrupt the event loop and take immediate control, or does the return of an async function re-enter the event loop as a new event at the end of the queue and wait its own turn again? In other words, is a callback from an async function treated like any other event, or differently? If it is treated differently, how? Is this a fundamentally stupid question?

The callback from an asynchronous function will go onto the queue, there isn't any way to "interrupt" the current flow of the event loop. The event loop does have multiple different orders so it may not be treated exactly like asynchronous IO events (I can't remember offhand), but a callback will still have to wait its turn.

  1. Can a callback be blocking, the same way as a badly written event handlers (e.g. HTTP request handler) can? In other words, can badly written code contained in a callback block the server? (I assume the answer is yes but want to know if I'm right.)

Yes, a callback is simply a function call and a function call is not asynchronous necessarily. You could write code like this that would block:

function add(a, b, cb) { cb(a + b) }

async.whilst(
  function() { return true },
  function(cb) { add(1 + 1, cb) },
  function() { console.log('Done!') }
)

While this code technically uses callbacks, it doesn't do anything asynchronous so Node will continue to evaluate the function calls and never check the event loop. That said, you see this less often because callbacks usually involve async operations or else they are not written to use callbacks.

Hopefully that helps, if anything wasn't clear, please let me know!

Chris Foster
  • 2,639
  • 2
  • 23
  • 30
  • Thanks for your answer, I have a followup question... I understand how a queue works and that Node can't pull from the event queue while processing. In my question I meant to ask: while Node is blocking, who manages the insertion of *new* client requests *onto* the queue? Is there a separate, dedicated thread for that? Do you happen to have any resources where I can do some additional reading on this? – Steverino Apr 03 '16 at 14:50
  • @fts_acer the queue is shared between threads. Whenever an event happens that is asynchronous, a separate I/O thread is used to handle that event. Since that thread shares the event queue with Node, when it is done the operation it pushes the result onto the queue and finishes execution. – Chris Foster Apr 04 '16 at 03:47
  • @fts_acer Since you're referring to "new client requests", I'm assuming you're talking about a socket server. When you make a call to `listen()`, a new thread begins listening on that port. In C, that call is blocking so the thread blocks until a request comes in. When it receives a request, it pushes it onto the queue, and loops back around to block until another request comes in. Node pulls those off the queue when it gets a chance. – Chris Foster Apr 04 '16 at 03:56
  • 1
    @fts_acer If you're more curious about the inner workings, I'd recommend reading through the [libuv documentation](http://libuv.org/). Node uses libuv to perform the asynchronous tasks. – Chris Foster Apr 04 '16 at 03:58
  • @fts_acer Just wanted to follow up with a reminder, if this answer (or one of the other ones here) solved your question please consider marking it as resolved! – Chris Foster Aug 31 '16 at 17:08
1

Recollecting from various sources, I read over time.

Node.js is not exactly single-threaded. It offloads Disk, DB, HTTP API calls to other threads, which once done enqueue their callbacks in event loop.

  1. There are other threads involved.
  2. Event loop should be a Queue-like data structure, which is accessed by many threads
  3. Mostly re-enters event loop (at end of queue). But there are ways to put it a front. (Need to check)
  4. Yes. E.g. if you use fs.readFileSync, it will block the server for some time.

Event loop: http://2014.jsconf.eu/speakers/philip-roberts-what-the-heck-is-the-event-loop-anyway.html

I have written from my memory. If this is helpful for you, I can try to lookup references.

Sangharsh
  • 2,999
  • 2
  • 15
  • 27
  • I tried to clarify my 4th question. Your answer is helpful. It confirms my suspicions in most cases. Any additional resources you might know of to help me get a deeper understanding would be extremely helpful, the resources I've found so far give only enough basic understanding for a developer to use, I'm trying to study how Node.js works under the hood. And thanks, you've helped already! – Steverino Apr 03 '16 at 06:57