Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How the event loop works #1118

Closed
marcoliceti opened this issue Feb 23, 2018 · 34 comments
Closed

How the event loop works #1118

marcoliceti opened this issue Feb 23, 2018 · 34 comments
Labels

Comments

@marcoliceti
Copy link

  • Node.js Version: Any
  • OS: Any
  • Scope: documentation

The official event loop documentation is here but it has a lot of unclear parts.

The first one is this:

poll: retrieve new I/O events; node will block here when appropriate

This is unclear because:

  • it uses the word "retrieve" (events), while all other phase descriptions use the word "execute" (callbacks)
  • it refers to I/O, which is confusing since there is already an "I/O callbacks" phase
  • what does exactly mean "node will block here when appropriate"?

So... let's move to the paragraphs where the "I/O callbacks" and "poll" phases are explained in detail.

I/O callbacks phase:

This phase executes callbacks for some system operations such as types of TCP errors. For example if a TCP socket receives ECONNREFUSED when attempting to connect, some *nix systems want to wait to report the error. This will be queued to execute in the I/O callbacks phase.

What?! Some paragraphs above it was said that:

I/O callbacks: executes almost all callbacks with the exception of close callbacks, the ones scheduled by timers, and setImmediate()

This sounds very different.

Anyways... let's move to the poll phase detailed explanation.

It starts with:

The poll phase has two main functions:

  1. Executing scripts for timers whose threshold has elapsed, then
  2. Processing events in the poll queue.

Again: what?!

It seems to me that point 1 is a repetition of the "timer" phase, while point 2 is a repetition of the "I/O callbacks" phase.

Note: The diagram also suggests that the poll phase relates to input only, while other parts of the article suggest that the poll phase relates both to input and to output.

Anyways, let's proceed with the "poll" phase detailed description:

When the event loop enters the poll phase and there are no timers scheduled, one of two things will happen:

  • If the poll queue is not empty, the event loop will iterate through its queue of callbacks executing them synchronously until either the queue has been exhausted, or the system-dependent hard limit is reached.
  • If the poll queue is empty, one of two more things will happen:
    • If scripts have been scheduled by setImmediate(), the event loop will end the poll phase and continue to the check phase to execute those scheduled scripts.
    • If scripts have not been scheduled by setImmediate(), the event loop will wait for callbacks to be added to the queue, then execute them immediately.

Once the poll queue is empty the event loop will check for timers whose time thresholds have been reached. If one or more timers are ready, the event loop will wrap back to the timers phase to execute those timers' callbacks.

Questions:

  1. What happens when the event loop enters the poll phase and there are timers scheduled?
  2. What happens when the system-dependent hard limit is reached?
  3. It seems that callbacks for timers whose thresholds have been reached are executed after emptying the poll queue. But this was previously previously presented as function number 1 (of 2) of the poll phase, so I ask myself if this kind of callbacks is executed only at the end of the poll phase or both at the beginning and at the end.

The timer phase detailed explanation provides an interesting example:

For example, say you schedule a timeout to execute after a 100 ms threshold, then your script starts asynchronously reading a file which takes 95 ms.

When the event loop enters the poll phase, it has an empty queue (fs.readFile() has not completed), so it will wait for the number of ms remaining until the soonest timer's threshold is reached. While it is waiting 95 ms pass, fs.readFile() finishes reading the file and its callback which takes 10 ms to complete is added to the poll queue and executed. When the callback finishes, there are no more callbacks in the queue, so the event loop will see that the threshold of the soonest timer has been reached then wrap back to the timers phase to execute the timer's callback. In this example, you will see that the total delay between the timer being scheduled and its callback being executed will be 105ms.

This is what I get from this example:

  • when the event loop enters the poll phase and there are timers scheduled, Node processes poll events (if any) or waits for them
  • it stops doing this whenever the poll queue is empty and at least on timer's threshold is reached
  • when this condition is met, Node goes back to the "timers" phase, i.e. starts a new iteration of the loop

Also, according to the detailed explanation of the "poll" phase, it seems to me that another termination condition for the poll phase is when the queue is empty and setImmediate work was scheduled. In that case Node goes on with the "check" phase.

So this is my final attempt to make sense of how the event loop actually works:

  1. During the "timers" phase setTimeout and setInterval callbacks whose thresholds are reached are executed.
  2. During the "I/O callbacks" phase I/O callbacks for completed (not pending) I/O are executed.
  3. During the "polling" phase Node waits for pending I/O to be completed. When an I/O operation completes, it goes into the poll event queue. Node processes this events immediately and while doing this, new events may be enqueued. In other words, the queue starts empty and then grows when pending I/O completes (and becomes shorter as poll events are processed). The poll phase stops when one of these happens: A) the poll event queue is empty and one or more timers "are ready", B) the poll event queue is empty and there is setImmediate work scheduled, C) a system-dependent hard limit (mesured in number of processed events) is met. In case A the event loop goes back to the "timers" phase, while in case B or C it goes on with the "check" phase.
  4. During the "check" phase any scheduled setImmediate work is executed.
  5. During the "close callbacks" phase any close events are processed.

This seems to me a consistent explanation that matches most of what is said in the documentation (which instead seems inconsistent).

Is this explanation correct?

Can we try to make the documentation more clear?

@vsemozhetbyt
Copy link

cc @nodejs/documentation, @trevnorris and @techjeffharris (as per nodejs/node#4936)

@plusice
Copy link

plusice commented Feb 28, 2018

I have one more question.
What if the queue is empty and the scheduled timers are not ready during the "polling" phase?Will event loop continue the loop and keep checking, which may occupies lots of CPU's time?

@marcoliceti
Copy link
Author

marcoliceti commented Feb 28, 2018

@plusice I think that's indirectly answered in the example provided in the timer phase explanation:

For example, say you schedule a timeout to execute after a 100 ms threshold, then your script starts asynchronously reading a file which takes 95 ms.

When the event loop enters the poll phase, it has an empty queue (fs.readFile() has not completed), so it will wait for the number of ms remaining until the soonest timer's threshold is reached. While it is waiting 95 ms pass, fs.readFile() finishes reading the file and its callback which takes 10 ms to complete is added to the poll queue and executed. When the callback finishes, there are no more callbacks in the queue, so the event loop will see that the threshold of the soonest timer has been reached then wrap back to the timers phase to execute the timer's callback. In this example, you will see that the total delay between the timer being scheduled and its callback being executed will be 105ms.

In short: when the queue is empty and scheduled timers are not ready, Node.js will remain in the polling phase.

I think that the general idea is that the polling phase is about responding quickly to I/O events as they occurr. At the same time, it's important not to delay too much ready timer callbacks, so as soon as the poll queue is empty Node.js temporarily stops "worrying" about I/O and executes ready timer callbacks (if any). While these callbacks are executed, new I/O events go into the I/O callbacks queue, not the poll queue (although this distinction is probably not so relevant).

Of course this is only my current understanding. It would be nice to have some a confirmation (or a different explanation).

@plusice
Copy link

plusice commented Mar 1, 2018

@marcoliceti Thank you for your explanation! And after reading some open source explanations of Node.js,I find that "polling phase" has a time-out.

  • If scripts have been scheduled by setImmediate(), "polling" phase will set a time-out which is zero.It means that after the queue has been exhausted, "polling" phase will not wait for callbacks to be added to the queue but continue to the check phase.
  • If scripts have been scheduled by setTimeout(), "polling" will set a time-out which is the result of the soonest threshold of timers minus current time.Then when time out, the loop continues and finally wraps back to the timers phase.

Actually, after reading the code I think the explanations of phase in the document make it harder to understand the process.For example, actually there are two "timers" checking in a loop.And what is called "wrap back to the timers phase" in the explanation:

Once the poll queue is empty the event loop will check for timers whose time thresholds have been reached. If one or more timers are ready, the event loop will wrap back to the timers phase to execute those timers' callbacks.

is just the second "timers" checking in the loop.We can see it from the code:

while (r != 0 && loop->stop_flag == 0) {
     // first timers
    uv__update_time(loop);
    uv__run_timers(loop);
    ran_pending = uv__run_pending(loop);
    uv__run_idle(loop);
    uv__run_prepare(loop);

    timeout = 0;
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT) {
      timeout = uv_backend_timeout(loop);
    }

    uv__io_poll(loop, timeout);
    uv__run_check(loop);
    uv__run_closing_handles(loop);

    if (mode == UV_RUN_ONCE) {
     // second timers
      uv__update_time(loop);
      uv__run_timers(loop);
    }

    r = uv__loop_alive(loop);
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }

From the code we can find that after the callbacks executing in "polling" phase(uv__io_poll), the "check"(uv__run_check) phase will execute before "timers" phase(uv__update_time, uv__run_timers).So this code will always log "immediate" first:

var fs = require('fs');

fs.readFile(__dirname + '/' + __filename, () => {
    setTimeout(() => {
        console.log('timeout')
    }, 100)
    setImmediate(() => {
        console.log('immediate')
    })
})

So...the same appeal, can we try to make the documentation more clear?

@marcoliceti
Copy link
Author

@plusice I found that the documentation of libuv is a great source for understanding how Node's event loop may work.

However, actual behavior depends on how libuv is used, so taking a look at Node's source code (as you did) is better.

Unfortunately, read and understand the source code is not so easy.

I think the best would be if some Node committer could help in fixing the documentation based on questions in this issue.

@plusice
Copy link

plusice commented Mar 2, 2018

@marcoliceti can't agree more

@gireeshpunathil
Copy link
Member

closing as answered, please let me know there is something outstanding in this.

@marcoliceti
Copy link
Author

@gireeshpunathil Please, re-open. There are no actual answers in this thread, just hypotheses from the same people asking the questions.

@gireeshpunathil
Copy link
Member

@marcoliceti - sure. I will see if I can explain your queries.

@Flet
Copy link

Flet commented Mar 29, 2018

This talk helped me get a better understanding of the event loop at a high level:

Philip Roberts: What the heck is the event loop anyway? | JSConf EU 2014

It may not get to the level of detail folks are looking for here, but it helped me understand. :)

@gireeshpunathil
Copy link
Member

@marcoliceti - I am back from vacation, so let us sort this out; if you have time. I guess we will need to run few iterations as many clarifications are required.

Do you want point-to-point answers to the questions you posted? I agree that the said doc is complex, but it makes sense, as the event loop is actually more complex than what is covered through the doc, the doc was a best effort attempt to cover as much internal detail as possible - every source line in the event loop is worth 10 lines of documentary explanation!

or do you want a basic understanding of the UV loop before even bringing the doc into the picture? there is an SOF answer I posted 2 years back when I was still learning the loop logic, but reading it now I believe that there is no incorrect information, and helps bring different perspectives: https://stackoverflow.com/questions/10680601/nodejs-event-loop/36859504#36859504

Third: One reference to the poll phase seem to have caused a lot of confusion.
/~https://github.com/libuv/libuv/blob/ff167ea5db66903c1a2f65b29ea0147b1108f2c9/src/unix/linux-core.c#L292

The timeout parameter that is passed to the poller primitive (uv__epoll_wait) is a key entity. As the loop has to manage both I/O workload as well as custom scheduled (deferred through timers) workload, the poller primitive is overloaded to carry both: return either with a set of I/O events that are ready to fire, or when the timeout has been elapsed - the timeout being a value carefully crafted to address any pending non-I/O deferred tasks.

If there are are no timers involved and if there are no I/O events ready at the moment, the poller primitive actually BLOCKs indefinitely, until an event has become ready.

This addresses at least 3 of your source of confusion i.e.:

poll: retrieve new I/O events; node will block here when appropriate

The poll phase has two main functions:

  1. Calculating how long it should block and poll for I/O, then
  2. Processing events in the poll queue.

What happens when the event loop enters the poll phase and there are timers scheduled?

I can explain more, but will wait for your response.

@marcoliceti
Copy link
Author

Hi @gireeshpunathil . Thanks for the reply!

This addresses at least 3 of your source of confusion

Yes, it does :)

There is still a couple of things that I don't understand about the poll phase, though.

First, it's not clear if timer callbacks are executed during the poll phase or not. The doc says they are:

The poll phase has two main functions:

  1. Executing scripts for timers whose threshold has elapsed, then
  2. Processing events in the poll queue.

But a few paragraphs later it seems that the poll phase just detects that there are timer callbacks ready to be executed (and then the loop wraps back to the timers phase for actual execution of these callbacks). I'm talking about this paragraph:

Once the poll queue is empty the event loop will check for timers whose time thresholds have been reached. If one or more timers are ready, the event loop will wrap back to the timers phase to execute those timers' callbacks.

So, are timer callbacks executed during the poll phase or not?

Also, what is the exact role of the I/O callbacks phase?

I mean, since the poll phase both retrieves and executes I/O events, what are the conditions under which an I/O event is handled in the I/O callbacks phase instead of the poll phase?

@gireeshpunathil
Copy link
Member

let me ping @bnoordhuis , as answering this requires fine grained knowledge, not an abstract one.

@gireeshpunathil
Copy link
Member

So, are timer callbacks executed during the poll phase or not?

No. timer callback are executed in the timer phase. However, the poll phase life cycle depends on the presence of timers in the loop. Example:

  • 3 timers registered with 100 ms, 1000 ms, and 2000 ms respectively
  • one socket from which you want to read (that will take 5 seconds as the remote is not writing now)
    As nothing ready at the moment when you enter the poll phase, the poller will have to take the minimum of 100, 1000 and 2000 as the timeout otherwise it will block for 5 seconds, and spoil the timer functions.

So the poll phase sets its timeout to 100ms, and polls.

Poll returns after 100ms, with no events (nfds = 0). I/O event is 5 seconds away.

So the poll phase returns.

As the uv loop (implemented by uv_run API) has loop in it, it goes back to the timer phase, and finding our first timer ready, fires it.

Also, what is the exact role of the I/O callbacks phase?

I/O event readiness causes I/O callbacks to be triggered. In this I/O callback phase however, there could be additional I/O requests. Example:

rs.on('data', (d) => {
 ws.write(d, () => {
 })
})

the 'data' handler callback is executed in I/O phase. Within that, there is a request to write into another I/O device, which is a fresh request. This triggers a new I/O event being registered with the loop after which the original I/O callback continues its execution. At appropriate time when the newly registered I/O becomes ready, the cycle repeats.

@marcoliceti
Copy link
Author

Regarding the answer to question 1...

Ok, timer callbacks are not executed during the poll phase. The document should be fixed. This:

The poll phase has two main functions:
1. Executing scripts for timers whose threshold has elapsed, then
2. Processing events in the poll queue.

Should become something like:

The poll phase has two main functions:
1. Poll for I/O events ready to be handled.
2. Execute handlers for these events.

Regarding the answer to question 2...

the 'data' handler callback is executed in I/O phase

Why?

Why not in the poll phase?

Again, what are the conditions under which an I/O event is handled in the I/O callbacks phase instead of the poll phase?

@gireeshpunathil
Copy link
Member

gireeshpunathil commented May 11, 2018

  1. Sure, feel free to raise a PR to that effect!
  2. Please read I/O phase as poll phase itself. There is no I/O phase, all I/Os emanate from the poll phase only.

same to the other question as well:

what are the conditions under which an I/O event is handled in the I/O callbacks?

I/O events are not handled in the I/O callbacks, they are detected in I/O callbacks, (as in the example code above), and then queued up in the loop for processing by the poll phase, in the next iteration of the loop.

Hope this helps!

@vsemozhetbyt
Copy link

@marcoliceti It seems in the new guide edition the second question is explained, see new scheme, renamed phase and especially:

https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#pending-callbacks

@vsemozhetbyt
Copy link

And the first issue seems also fixed:

The poll phase has two main functions:

  1. Calculating how long it should block and poll for I/O, then
  2. Processing events in the poll queue.

@marcoliceti
Copy link
Author

@gireeshpunathil

Please read I/O phase as poll phase itself. There is no I/O phase

Thanks. This is what I suspected / wanted to hear. It was not clear from the original document.

Actually, I noticed right now that the document has changed a bit since I opened this issue. There is no more an I/O callbacks phase in the diagram.

Also, I see that executing timer callbacks is no longer listed as a function which the poll phase is responsible for. Perfect.

So, to recap, here is my current understanding of the event loop:

  1. At any given time there is a (possibly empty) queue of I/O events.
  2. Periodically, Node checks this queue.
  3. If it's not empty, Node dequeues I/O events until the queue becomes empty (note: dequeue = execute callback).
  4. When the queue is empty, Node starts polling for new I/O events.
  5. The polling goes on until a new I/O event is fired (or the time to execute a setTimeout / setInterval callback has come).
  6. In general, while an I/O event is being processed new I/O event may join the queue (note: process = execute callback).
  7. As a consequence, even when the queue becomes empty its length may later grow again to a large number. And then it may go back to zero. Multiple times.
  8. Each time the queue is empty, Node checks if there are setTimeout and setInterval callbacks to execute.
  9. If there are, Node executes them and then switches back to polling / processing I/O events.
  10. These procedure is repeated until there are no more setTimeouts / setIntervals / async I/O to wait for, at which point Node exits.

Notes:

  • basically, points 2-7 correspond to the poll phase
  • points 8 a and 9 express the switch from poll phase to timers phase and vice versa
  • point 5 is achieved through a timeout for the polling based on the soonest setTimeout / setInterval

The explanation I gave so far can be refined in order to cover also setImmediate and process.nextTick:

  • setImmediate callbacks are executed as soon as the I/O event queue becomes empty (and thus have a somewhat higher priority than setTimeout / setInterval callbacks)
  • process.nextTick callbacks are executed at the end of the current "phase", where a phase can be one of the following:
    • execution of setTimeout / setInterval callbacks ("timers phase")
    • processing / polling I/O events ("poll phase")
    • execution of setImmediate callbacks ("check phase")

Note: there are other phases, but I think that for a high level discussion this is enough.

@gireeshpunathil , can you confirm that this is correct?

@marcoliceti
Copy link
Author

@vsemozhetbyt Yes, I noticed the update this morning :)

@gireeshpunathil
Copy link
Member

@marcoliceti - This is fantastic, looks great! few minor nits:

  1. The polling goes on until a new I/O event is fired (or the time to execute a setTimeout / setInterval callback has come).

The polling goes on until a new I/O event is fired (or the time to execute a setTimeout / setInterval callback has come) or a system hard limit has reached. For example what if new events are always getting ready when you poll? It starves the rest of the system. So (in Linux) if you get mouthful (1024) of events for 48 consecutive iterations, the loop stops the current iteration.

  1. If there are, Node executes them and then switches back to polling / processing I/O events.

9a. If there are none, and there are pending I/O events, Node goes into blocking (indefinite) poll until at least one event becomes ready.

@marcoliceti
Copy link
Author

The polling goes on until a new I/O event is fired (or the time to execute a setTimeout / setInterval callback has come) or a system hard limit has reached.

I wanted to add that as a note, but then I forgot it. Thanks!

9a. If there are none, and there are pending I/O events, Node goes into blocking (indefinite) poll until at least one event becomes ready.

Yes, but I think this is an implicit consequence of point 5 and the fact the it is implemented with a timeout based on the soonest setTimeout / setInterval: if there are no pending setTimeouts / setIntervals then there's no timeout for the polling.

Ok...

I think that finally everything is clear enough. Thank you all for the help :)

@gireeshpunathil
Copy link
Member

No problem, happy to know that you are able to gain improved insight into the event machinery of Node.js that is central to the asynchronous event driven philosophy.

I invite you to have a look at project items that suit your interest and see if you are willing to participate, based on your bandwidth and skill.

Node.js

Feature request backlog: /~https://github.com/nodejs/node/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature+request%22
Looking for contributors: /~https://github.com/nodejs/node/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22
Looking for first timers: /~https://github.com/nodejs/node/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22
Issue backlog: /~https://github.com/nodejs/node/issues
Support backlog: /~https://github.com/nodejs/help/issues

libuv:

Feature request backlog: /~https://github.com/libuv/libuv/issues?q=is%3Aissue+is%3Aopen+label%3Afeature-request
Looking for contributors: /~https://github.com/libuv/libuv/issues?q=is%3Aissue+is%3Aopen+label%3Ahelp-wanted
Looking for first timers: /~https://github.com/libuv/libuv/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-contribution
Issue backlog: /~https://github.com/libuv/libuv/issues
Support backlog: /~https://github.com/libuv/help/issues

If you want to jump start, here is a quick PR for you to get upto speed:
Fix the grammer in this line :)

The complete PR process is documented here

Wishing you a great success with the project!

@marcoliceti
Copy link
Author

I invite you to have a look at project items that suit your interest and see if you are willing to participate, based on your bandwidth and skill.

Sure, I'll see what I can do :)

@gireeshpunathil
Copy link
Member

purpose of this issue has been addressed, closing this then, cheers!

@octanium
Copy link

octanium commented May 24, 2021

@marcoliceti @gireeshpunathil
There are two questions regarding event loop phases in Nodejs.

  1. Let me know if following point is correct.
    If callback queue of poll phase is empty and there are callbacks of setImmediate, then the event loop ends poll phase and immediately jumps to the check phase. (even if the timers are ready, it will not jump to timers phase.)

  2. Now the callback queue of Poll phase is not empty, and the polling time is say 200ms(min of one of the timeouts). say all the poll-callbacks took 100ms and now the poll phase is idle again for 100ms. Considering Timer(200ms) is ready and there are callbacks in setimmediate
    case 1: Jumps immediately to the check phase(because it is idle and there are callbacks for setimmediate).
    Or
    Case 2: will perform poll for another 100ms and at the end jumps to the timers phase (because timer is ready).

Which one of the above case will execute?

Also,
(If case:2 is true, then the check phase might starve if there are a number of timers and I/O. It will keep looping between these phases and will never reach check phase)

@marcoliceti
Copy link
Author

Hi @octanium sorry for the late reply, I'll try to answer but keep in mind that:

  • 3 years have passed since I opened this issue and my Node skills are no longer that good, but at least I tried to read again all the posts in this issue
  • I'm not (and never have been) a Node committer: I'm just a user like you
  • Node (and, specifically, its event loop) may have changed in the last 3 years

That being said, here are my answers:

  1. Correct.
  2. I'm not sure I understand your scenario. What I can tell you is that when the I/O queue is empty Node executes setImmediate / setTimeout / setInterval callbacks, if any. When there are setImmediate callbacks, they are executed before any setTimeout / setInterval callback. When all these callbacks have been executed, Node goes back to checking the I/O queue. If it's empty, it starts polling for new I/O events, otherwise it processes those currently in the queue until the queue becomes empty again.

@octanium
Copy link

octanium commented Jun 19, 2021

Thanks for the reply @marcoliceti . Appreciate it.
Just a general Question.
Where can I learn more about Nodejs so that I can understand the indepths/architecture with practical examples.

@techjeffharris
Copy link

techjeffharris commented Nov 6, 2021

I'm really glad that this guide has been able to help people better understand Node and how the even loop works. Getting something official into the project that could be then maintained and improved by the community was my goal and it appears that has certainly happened.

@marcoliceti is in a similar boat as myself, i.e., all of the bullet points in his most recent post are true for me as well. I have not been doing much Node development in the years since the guide was accepted due to other life requirements. With any luck, that might be changing soon, though 🤞.

Furthermore, this guide was probably my first and quite possibly my only hard contribution to Node (it has been a long time since I was really involved), as I was at the time a Node enthusiest watching the iojs fork bloom before being merged back into Node under governance of the newly formed Nodejs Foundation. With a penchant for making complex topics easier to reason about, I was mostly trying to help stitch together bits of knowledge and wisdom that I had heard from others and trying my best to make the topic easier to mentally digest by removing ambiguities and trying to make verbiage and references more consistent. I got a LOT of help from prominent developers at the time. Part of me wishes I had more explicitly mentioned them for their hepful assistance and conversational contributions, though IIRC that pull request was open for some time and folks involved could have requested attribution if they desired, so oh well I suppose. 🤷‍♂️

TL;DR - My conceptual understanding of how stuff happens is sufficient for my needs but it is not developed enough to answer your second question, @octanium. It is worth nothing that are some os specific implementation details that could impact the answer to your question. For example, the code that @plusice referenced this comment was referenced from node/deps/uv/src/unix/core.c which itself is specific to UNIX-like operating systems.

If you are concerned about the check phase starving, you could try creating some tests to recreate the scenario you describe. If you're able to recreate the issue that concerns you, I suggest you review the contribution guide, which contains instructions for raising issues.

@octanium
Copy link

octanium commented Nov 7, 2021

Thanks @techjeffharris.

@basavaraj96
Copy link

What happens in case the system dependent hard limit is reached but no timers are yet "ready" (block time that was calculated by the event loop at the start of the poll phase has not yet elapsed)? Does the event loop proceed to the check phase? Or does it go back to the timers phase and once again come back to the poll phase with a new block time? In other words, this is the scenario where the event loop cannot execute any timer callback because no timer is ready and it cannot also remain in the poll phase because the hard limit has been reached.

@techjeffharris
Copy link

techjeffharris commented Nov 8, 2021

@basavaraj96 - IIRC the hard limit is a quantity of callbacks executed rather than a duration of time. Based on that and my understanding of your question, if the number of callbacks processed in Poll phase reaches the hard limit, the event loop will simply move onto the next phase which gives the other phases the opportunity to perform their tasks, then eventually loop back around to the Poll phase again.

Does this paragraph from the doc help clarify or am I missing something in your question?

Each phase has a FIFO queue of callbacks to execute. While each phase is special in its own way, generally, when the event loop enters a given phase, it will perform any operations specific to that phase, then execute callbacks in that phase's queue until the queue has been exhausted or the maximum number of callbacks has executed. When the queue has been exhausted or the callback limit is reached, the event loop will move to the next phase, and so on.

@basavaraj96
Copy link

basavaraj96 commented Nov 9, 2021

@techjeffharris This answers my question. Thanks for the explanation. I feel the docs are a bit misleading in some places. For example, consider:
"When the queue has been exhausted or the callback limit is reached, the event loop will move to the next phase, and so on".
This sentence is applicable to all the other phases but not to the poll phase. In the poll if the callback limit is reached then, yes, definitely it moves to the next phase (as previously explained by you). But if the queue is exhausted and the block time (shortest timer's delay) has not yet elapsed, then event loop will continue to be in the poll phase (assuming there are no setInterval callbacks).

@Juanpam
Copy link

Juanpam commented Jul 21, 2022

@marcoliceti @gireeshpunathil Hello! Sorry to revisit this thread. I was also researching about Node.js Event Loop and found the docs confusing as well. I've read this whole thread and while it clarified most of my understanding I still don't understand what the now called "pending callbacks" phase is about.

GIven what is explained on this post, I/O events are "detected/received" in the poll phase and the callbacks for said events are also executed, "handing" the event.

However, if a timer is up then the poll phase is over and the loop starts again at the timers phase. That means callbacks related to I/O events (e.g file is done loading) could be left to execute. From my understanding that's where the "pending calback" takes place, but in the docs it is only stated:

This phase executes callbacks for some system operations such as types of TCP errors. For example if a TCP socket receives ECONNREFUSED when attempting to connect, some *nix systems want to wait to report the error. This will be queued to execute in the pending callbacks phase.

At the same time, this is what is found also on a previous comment:

  1. Sure, feel free to raise a PR to that effect!
  2. Please read I/O phase as poll phase itself. There is no I/O phase, all I/Os emanate from the poll phase only.

same to the other question as well:

what are the conditions under which an I/O event is handled in the I/O callbacks?

I/O events are not handled in the I/O callbacks, they are detected in I/O callbacks, (as in the example code above), and then queued up in the loop for processing by the poll phase, in the next iteration of the loop.

Hope this helps!

I get what was stated here is that there is no I/O phase and that callbacks are deferred till the next loop, looking similar to what "pending callbacks" phase sounds like.

Could you please clarify this for me if possible? I think some little comment about this kind of scenarios or examples could improve the docs significantly and would be happy to submit a PR to the docs but I need to understand it better first 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants