-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
loop: avoid double calling the timers in UV_RUN_ONCE mode #3791
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a couple of ideas for simplification.
Though, not sure I'm sold on the idea of not running timers after handling an I/O event. For example:
- I use a
uv_prepare_t
for gathering metrics (something I do a lot of) - In the
uv_prepare_cb
I set a timer for 10ms - Event loop polls for 9ms before receiving an event
- Processing the event takes 2ms
- The final phase of the loop would have triggered the timer because the poll time + event processing time > timer timeout, but with this change the timer would no longer fire.
} | ||
|
||
loop->watchers[loop->nwatchers] = NULL; | ||
loop->watchers[loop->nwatchers + 1] = NULL; | ||
|
||
if (have_signals != 0) | ||
return; /* Event loop should cycle now so don't poll again. */ | ||
return did_work; /* Event loop should cycle now so don't poll again. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be enough to return whether nevents > 0
, and simply return 0
in every location above this.
Should be pretty much the same wherever nevents
is used in other locations.
@@ -414,7 +416,7 @@ int uv_run(uv_loop_t* loop, uv_run_mode mode) { | |||
uv__run_check(loop); | |||
uv__run_closing_handles(loop); | |||
|
|||
if (mode == UV_RUN_ONCE) { | |||
if (mode == UV_RUN_ONCE && did_work == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, you could remove all the extra code in each uv__io_poll()
by caching the uv__loop_metrics_t::metrics::events
value at the top of the while loop then comparing it here. For example, at the top set:
uint64_t events_count = uv__get_loop_metrics(loop)->metrics.events
and here just check
events_count == uv__get_loop_metrics(loop)->metrics.events
Would still need to check if timers did any work since they're not counted as events.
Well, with the double processing of timers, if the first timer was processed before the polling, yours is processed during the second pass. If not - then it is not. This is the major problem with the current situation - it is not very deterministic. Otherwise you can probably find some counter-examples, but the most important is that the overall behavior is completely deterministic. For the timers, either there is a definition that the time is measured relative to the beginning of the processing of the timers queue - either there are as many passes as necessary to process all the timers that might have expired after taking into account the additional time spent in the handlers. |
@mmomtchev
This won't have any change to running with How Actually, you've got me thinking. I might have a solution that's v1.x compatible. I just tested it and all libuv and node.js tests pass, along with fixing your issue. I'll open a PR with the patch and ping you. Thanks for investigating this and submitting a PR. |
Here's the patch: #3927 |
superseded by #3927 |
This change, which is less invasive that it might appear, tracks when each of the event loop routines actually did any work and double calls the timers only if no other handlers have been called.
It implements a solution to both #3686 (unit test
timer_no_double_call
) andtimer_run_once
(PR #41)