-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
UV_RUN_ONCE
breaks the usual order of timers / poll in the event loop
#3686
Comments
Refs: libuv/libuv#3686 Waiting to see if this is supposed to be defined behavior
Refs: libuv/libuv#3686 Waiting to see if this is supposed to be defined behavior
Refs: libuv/libuv#3686 Waiting to see if this is supposed to be defined behavior
The commit log of commit f6d8ba3 explains why it's necessary to run timers. I think it'd be okay to change the current behavior as long as something runs. In the special case of |
I would possibly argue we could remove the timers from the top of the loop and make them unconditional at the end, since (except for the first iteration) it currently just runs the timers twice in a row (for UV_RUN_ONCE mode). But needs testing |
That's probably an observable change in behavior. Node's documentation is pretty explicit about what phases run when so chances are good that change would break someone's code. I guess Node's test suite would uncover that since it's got pretty exhaustive event loop coverage. |
My proposed theory is that the last phase of the loop and the first phase of the next loop are (or at least should be) indistinguishable to a sensible consumer, since they differ only by the placement of the |
@vtjnash @bnoordhuis I just ran all of Node.js' unit tests after moving |
Worst case we can always revert such a change but I'd feel kind of bad if someone ends up spending half a day debugging a timing issue because we were a little too cavalier. Apart from that no strong opinion. |
Breaking a test that is already broken doesn't sound too bad. But we can make it a v2 fix, if it seems to breaking now. |
Refs: libuv/libuv#3686 Waiting to see if this is supposed to be defined behavior
Refs: libuv/libuv#3686 Waiting to see if this is supposed to be defined behavior
Refs: libuv/libuv#3686 Waiting to see if this is supposed to be defined behavior
Refs: libuv/libuv#3686 Waiting to see if this is supposed to be defined behavior
This issue has been automatically marked as stale because it has not had recent activity. Thank you for your contributions. |
I will submit a PR |
Refs: libuv/libuv#3686 Waiting to see if this is supposed to be defined behavior
Refs: libuv/libuv#3686 Waiting to see if this is supposed to be defined behavior
Refs: libuv/libuv#3686 Waiting to see if this is supposed to be defined behavior
Refs: libuv/libuv#3686 Waiting to see if this is supposed to be defined behavior
@bnoordhuis @vtjnash I made a dummy Node.js PR with this change - there is one ASAN test that timed out and I do not know if it is related or not - I found other PRs where this exact test timing out in the ASAN build was the only error but it is not known as a flaky test. I will try investigating further. Still, I think that this change will make lots of people very nervous and I will definitely not be able to push this through. I am still hesitating if the best way to fix this is not to simply return the expired timer and then to run it. |
Here is my raw libuv repro: int timer_check_double_call_called = 0;
static void timer_check_double_call(uv_timer_t* handle) {
ASSERT(1 != timer_check_double_call_called);
timer_check_double_call_called = 1;
}
TEST_IMPL(timer_no_double_call) {
uv_timer_t timer_handle;
const uint64_t timeout_ms = 10;
ASSERT(0 == uv_timer_init(uv_default_loop(), &timer_handle));
ASSERT(0 ==
uv_timer_start(&timer_handle, timer_check_double_call, timeout_ms, timeout_ms));
uv_sleep(timeout_ms * 2);
ASSERT(1 == uv_run(uv_default_loop(), UV_RUN_ONCE));
ASSERT(1 == timer_check_double_call_called);
uv_close((uv_handle_t*)&timer_handle, NULL);
MAKE_VALGRIND_HAPPY();
return 0;
} |
#3791 is the alternative solution - it appears very invasive but in fact it has less consequences - instead of modifying the order of the event loop, it tracks if any of the event loop routines did any work, and then double calls the timer only if no work was done during the current call. |
I am proposing #3791 as a less intrusive change - but should you decide otherwise - I can proceed with exchanging the event loop order - which is a two-line change that is somewhat riskier. Ping me once you have set your priorities straight. |
The maximum number of times timers should run when uv_run() is called with UV_RUN_ONCE and UV_RUN_NOWAIT is 1. Do that by conditionally calling timers before entering the while loop when called with UV_RUN_DEFAULT. The reason to always run timers at the end of the while loop, instead of at the beginning, is to help enforce the conceptual event loop model. Which starts when entering the event provider (e.g. calling poll). Other than only allowing timers to be processed once per uv_run() execution, the only other noticeable change this will show is if all the following are true: * uv_run() is called with UV_RUN_NOWAIT or UV_RUN_ONCE. * An event is waiting to be received when poll is called. * Execution time between the call to uv_timer_start() and entering the while loop is longer than the timeout. If all these are true, then timers that would have executed before entering the event provider will now be executed afterward. Fixes: libuv#3686 Co-authored-by: Momtchil Momtchev <momtchil@momtchev.com>
Sorry to come here after all this time, but this change has broken my app (txiki.js) and I wonder if others could be affected too. Here is how my code works in a nutshell:
After this change, timers run last, and thus my check handle hasn't run as the last thing, which is what I used to keep the loop alive by starting the idle handle. This means the loop could exit early, if new promises were created on a timer, since check handles no longer run afterwards. I think this change in behavior is wrong. |
In libuv/libuv#3686 libuv changed when timers run respective to check handles. Change the way we run the loop by running it again if necessary, untill there is no more work to do of the loop has been stopped. Fixes: #443
In libuv/libuv#3686 libuv changed when timers run respective to check handles. Change the way we run the loop by running it again if necessary, untill there is no more work to do of the loop has been stopped. Fixes: #443
In libuv/libuv#3686 libuv changed when timers run respective to check handles. Change the way we run the loop by running it again if necessary, untill there is no more work to do of the loop has been stopped. Fixes: #443
This code:
libuv/src/unix/core.c
Line 423 in e81cc74
is meant to run when
uv__io_poll()
returns without having called any I/O so that it can run at least one of the timers.However it makes no such checks at all - in fact it is simply a second entry point for running the timers.
This deviates from the normal flow where all timers run, then all sockets are polled.
In particular this otherwise valid Node.js unit test becomes flaky if used on an event loop with
UV_RUN_ONCE
- where the order can betimers - sockets
ortimers - timers - sockets
: https://github.com/nodejs/node/blob/main/test/sequential/test-timers-block-eventloop.jsIs this really a bug, or is it simply undefined behaviour (on which Node.js has come to rely)?
The text was updated successfully, but these errors were encountered: