-
Notifications
You must be signed in to change notification settings - Fork 285
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
Comments
cc @nodejs/documentation, @trevnorris and @techjeffharris (as per nodejs/node#4936) |
I have one more question. |
@plusice I think that's indirectly answered in the example provided in the timer phase explanation:
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). |
@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.
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:
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( 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? |
@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. |
@marcoliceti can't agree more |
closing as answered, please let me know there is something outstanding in this. |
@gireeshpunathil Please, re-open. There are no actual answers in this thread, just hypotheses from the same people asking the questions. |
@marcoliceti - sure. I will see if I can explain your queries. |
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. :) |
@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. 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 If there are are no timers involved and if there are no I/O events ready at the moment, the poller primitive actually This addresses at least 3 of your source of confusion i.e.:
I can explain more, but will wait for your response. |
Hi @gireeshpunathil . Thanks for the reply!
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:
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:
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? |
let me ping @bnoordhuis , as answering this requires fine grained knowledge, not an abstract one. |
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:
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.
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. |
Regarding the answer to question 1... Ok, timer callbacks are not executed during the poll phase. The document should be fixed. This:
Should become something like:
Regarding the answer to question 2...
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? |
same to the other question as well:
I/O events are not Hope this helps! |
@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 |
And the first issue seems also fixed:
|
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:
Notes:
The explanation I gave so far can be refined in order to cover also
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? |
@vsemozhetbyt Yes, I noticed the update this morning :) |
@marcoliceti - This is fantastic, looks great! few minor nits:
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.
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. |
I wanted to add that as a note, but then I forgot it. Thanks!
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 Ok... I think that finally everything is clear enough. Thank you all for the help :) |
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 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 libuv: Feature request backlog: /~https://github.com/libuv/libuv/issues?q=is%3Aissue+is%3Aopen+label%3Afeature-request If you want to jump start, here is a quick PR for you to get upto speed: The complete PR process is documented here Wishing you a great success with the project! |
Sure, I'll see what I can do :) |
purpose of this issue has been addressed, closing this then, cheers! |
@marcoliceti @gireeshpunathil
Which one of the above case will execute? Also, |
Hi @octanium sorry for the late reply, I'll try to answer but keep in mind that:
That being said, here are my answers:
|
Thanks for the reply @marcoliceti . Appreciate it. |
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. |
Thanks @techjeffharris. |
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. |
@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?
|
@techjeffharris This answers my question. Thanks for the explanation. I feel the docs are a bit misleading in some places. For example, consider: |
@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:
At the same time, this is what is found also on a previous comment:
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 😅 |
The official event loop documentation is here but it has a lot of unclear parts.
The first one is this:
This is unclear because:
So... let's move to the paragraphs where the "I/O callbacks" and "poll" phases are explained in detail.
I/O callbacks phase:
What?! Some paragraphs above it was said that:
This sounds very different.
Anyways... let's move to the poll phase detailed explanation.
It starts with:
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:
Questions:
The timer phase detailed explanation provides an interesting example:
This is what I get from this example:
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:
setTimeout
andsetInterval
callbacks whose thresholds are reached are executed.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.setImmediate
work is executed.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?
The text was updated successfully, but these errors were encountered: