While reading the libuv documentation, I had some questions about pending and poll phases of Event Loop. In the Design Overview section they are described as follows:
- Pending callbacks are called. All I/O callbacks are called right after polling for I/O, for the most part. There are cases, however, in which calling such a callback is deferred for the next loop iteration. If the previous iteration deferred any I/O callback it will be run at this point.
...
Poll timeout is calculated. Before blocking for I/O the loop calculates for how long it should block. These are the rules when calculating the timeout.
The loop blocks for I/O. At this point the loop will block for I/O for the duration calculated in the previous step. All I/O related handles that were monitoring a given file descriptor for a read or write operation get their callbacks called at this point.
My main question is: poll timeout only limits the time to wait for new events from the system event notification mechanism or it limits the total execution time available to the poll phase?
Based on the answer to this question, I can assume several scenarios.
Scenario 1
The timeout only limits the time to wait for events from the system event notification mechanism:
- Pending Callbacks are called.
- Poll timeout is calculated.
- Libuv subscribes to the system event notification mechanism and puts all events that have occurred into the poll queue. When a timeout is reached, it moves to the next step.
- Then it executes all callbacks from the poll queue. When the poll queue is empty it moves on to the next step.
This is how I understand the execution of the pending and poll phases in libuv, also, something similar to this scenario is described in the NodeJS documentation
If this scenario is correct, I have a question for step three: Does Libuv continuously wait for events from the event notification mechanism until a timeout is reached, or it does take some breaks to process the events from the poll queue?
Scenario 2
Some authors say that timeout limits the total execution time of the poll phase, and callbacks from the poll queue that ran out of time are placed in the pending queue.
- Pending callbacks are called.
- Poll timeout is calculated.
- Poll phase executes until timeout is reached.
- Callbacks from the poll queue that ran out of time due to timeout are placed in the pending queue to be executed at the next iteration of the Event Loop.
If this scenario is correct, how does libuv calculate the time required to execute a callback? Or it just checks the timeout and if it is not exceeded, it runs the callback without paying attention to the time it takes to execute?
Questions
My general questions is:
- Which of the scenarios is closer to the truth or they are both incorrect?
- Does poll queue callbacks are handled if poll timeout is zero?
- What is the purpose of pending queue and when does a handler from the poll queue end up in the pending queue?
- Also, some authors say that callbacks of thread pool operations go to the pending queue, instead of poll queue. Is that correct?
Libuv's event loop can sometimes be a bit complex to understand, but I'll do my best to clarify your questions.
Scenario 1: The poll timeout in Libuv typically limits the time to wait for new events from the system event notification mechanism. Your understanding of the execution steps is mostly correct. Libuv will wait for events from the event notification mechanism until the timeout is reached. If the timeout is reached before any events occur, it will move on to executing any pending callbacks. It will not take breaks to process events from the poll queue while waiting for events from the system notification mechanism because that would defeat the purpose of efficient event-driven I/O.
Scenario 2: This scenario is not entirely accurate. Libuv's poll timeout usually limits the time spent waiting for events from the system event notification mechanism. If the timeout is reached before any events occur, it will move on to execute pending callbacks. Callbacks from the poll queue are typically executed during the poll phase, and they don't get placed in the pending queue based on execution time. Libuv doesn't have a built-in mechanism to monitor the execution time of individual callbacks and move them to the pending queue based on that.
To address your specific questions:
Scenario 1 is closer to the truth. Libuv waits for events from the system event notification mechanism until the timeout is reached, and it doesn't take breaks to process events from the poll queue.
Callbacks in the poll queue are usually handled during the poll phase, and the poll timeout primarily limits the time waiting for new events from the system event notification mechanism. If the poll timeout is zero, Libuv may not wait at all and will immediately move on to execute pending callbacks.
The purpose of the pending queue is to handle callbacks that are scheduled to run later, typically callbacks that were scheduled during the current event loop iteration or deferred callbacks from previous iterations.
Thread pool operations, such as those used for file system operations or certain asynchronous tasks, do not go directly to the poll queue. Instead, they are typically handled by separate worker threads. Results or completion callbacks from these thread pool operations may be posted to the event loop's pending queue for execution once the main event loop is ready to handle them. This is separate from the poll phase.