Skip to content

Commit 6600954

Browse files
win,unix: change execution order of timers (#3927)
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: #3686 Co-authored-by: Momtchil Momtchev <[email protected]>
1 parent 4a65e10 commit 6600954

6 files changed

Lines changed: 74 additions & 36 deletions

File tree

docs/src/design.rst

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,15 @@ stages of a loop iteration:
6060
:align: center
6161

6262

63-
#. The loop concept of 'now' is updated. The event loop caches the current time at the start of
64-
the event loop tick in order to reduce the number of time-related system calls.
63+
#. The loop concept of 'now' is initially set.
64+
65+
#. Due timers are run if the loop was run with ``UV_RUN_DEFAULT``. All active timers scheduled
66+
for a time before the loop's concept of *now* get their callbacks called.
6567

6668
#. If the loop is *alive* an iteration is started, otherwise the loop will exit immediately. So,
6769
when is a loop considered to be *alive*? If a loop has active and ref'd handles, active
6870
requests or closing handles it's considered to be *alive*.
6971

70-
#. Due timers are run. All active timers scheduled for a time before the loop's concept of *now*
71-
get their callbacks called.
72-
7372
#. Pending callbacks are called. All I/O callbacks are called right after polling for I/O, for the
7473
most part. There are cases, however, in which calling such a callback is deferred for the next
7574
loop iteration. If the previous iteration deferred any I/O callback it will be run at this point.
@@ -101,9 +100,11 @@ stages of a loop iteration:
101100
#. Close callbacks are called. If a handle was closed by calling :c:func:`uv_close` it will
102101
get the close callback called.
103102

104-
#. Special case in case the loop was run with ``UV_RUN_ONCE``, as it implies forward progress.
105-
It's possible that no I/O callbacks were fired after blocking for I/O, but some time has passed
106-
so there might be timers which are due, those timers get their callbacks called.
103+
#. The loop concept of 'now' is updated.
104+
105+
#. Due timers are run. Note that 'now' is not updated again until the next loop iteration.
106+
So if a timer became due while other timers were being processed, it won't be run until
107+
the following event loop iteration.
107108

108109
#. Iteration ends. If the loop was run with ``UV_RUN_NOWAIT`` or ``UV_RUN_ONCE`` modes the
109110
iteration ends and :c:func:`uv_run` will return. If the loop was run with ``UV_RUN_DEFAULT``

docs/src/static/loop_iteration.png

-15 KB
Loading

src/unix/core.c

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -390,10 +390,17 @@ int uv_run(uv_loop_t* loop, uv_run_mode mode) {
390390
if (!r)
391391
uv__update_time(loop);
392392

393-
while (r != 0 && loop->stop_flag == 0) {
394-
uv__update_time(loop);
393+
/* Maintain backwards compatibility by processing timers before entering the
394+
* while loop for UV_RUN_DEFAULT. Otherwise timers only need to be executed
395+
* once, which should be done after polling in order to maintain proper
396+
* execution order of the conceptual event loop. */
397+
if (mode == UV_RUN_DEFAULT) {
398+
if (r)
399+
uv__update_time(loop);
395400
uv__run_timers(loop);
401+
}
396402

403+
while (r != 0 && loop->stop_flag == 0) {
397404
can_sleep =
398405
QUEUE_EMPTY(&loop->pending_queue) && QUEUE_EMPTY(&loop->idle_handles);
399406

@@ -424,18 +431,8 @@ int uv_run(uv_loop_t* loop, uv_run_mode mode) {
424431
uv__run_check(loop);
425432
uv__run_closing_handles(loop);
426433

427-
if (mode == UV_RUN_ONCE) {
428-
/* UV_RUN_ONCE implies forward progress: at least one callback must have
429-
* been invoked when it returns. uv__io_poll() can return without doing
430-
* I/O (meaning: no callbacks) when its timeout expires - which means we
431-
* have pending timers that satisfy the forward progress constraint.
432-
*
433-
* UV_RUN_NOWAIT makes no guarantees about progress so it's omitted from
434-
* the check.
435-
*/
436-
uv__update_time(loop);
437-
uv__run_timers(loop);
438-
}
434+
uv__update_time(loop);
435+
uv__run_timers(loop);
439436

440437
r = uv__loop_alive(loop);
441438
if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)

src/win/core.c

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -609,10 +609,17 @@ int uv_run(uv_loop_t *loop, uv_run_mode mode) {
609609
if (!r)
610610
uv_update_time(loop);
611611

612-
while (r != 0 && loop->stop_flag == 0) {
613-
uv_update_time(loop);
612+
/* Maintain backwards compatibility by processing timers before entering the
613+
* while loop for UV_RUN_DEFAULT. Otherwise timers only need to be executed
614+
* once, which should be done after polling in order to maintain proper
615+
* execution order of the conceptual event loop. */
616+
if (mode == UV_RUN_DEFAULT) {
617+
if (r)
618+
uv_update_time(loop);
614619
uv__run_timers(loop);
620+
}
615621

622+
while (r != 0 && loop->stop_flag == 0) {
616623
can_sleep = loop->pending_reqs_tail == NULL && loop->idle_handles == NULL;
617624

618625
uv__process_reqs(loop);
@@ -645,18 +652,8 @@ int uv_run(uv_loop_t *loop, uv_run_mode mode) {
645652
uv__check_invoke(loop);
646653
uv__process_endgames(loop);
647654

648-
if (mode == UV_RUN_ONCE) {
649-
/* UV_RUN_ONCE implies forward progress: at least one callback must have
650-
* been invoked when it returns. uv__io_poll() can return without doing
651-
* I/O (meaning: no callbacks) when its timeout expires - which means we
652-
* have pending timers that satisfy the forward progress constraint.
653-
*
654-
* UV_RUN_NOWAIT makes no guarantees about progress so it's omitted from
655-
* the check.
656-
*/
657-
uv_update_time(loop);
658-
uv__run_timers(loop);
659-
}
655+
uv_update_time(loop);
656+
uv__run_timers(loop);
660657

661658
r = uv__loop_alive(loop);
662659
if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)

test/test-list.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@ TEST_DECLARE (timer_from_check)
233233
TEST_DECLARE (timer_is_closing)
234234
TEST_DECLARE (timer_null_callback)
235235
TEST_DECLARE (timer_early_check)
236+
TEST_DECLARE (timer_no_double_call_once)
237+
TEST_DECLARE (timer_no_double_call_nowait)
236238
TEST_DECLARE (idle_starvation)
237239
TEST_DECLARE (idle_check)
238240
TEST_DECLARE (loop_handles)
@@ -842,6 +844,8 @@ TASK_LIST_START
842844
TEST_ENTRY (timer_is_closing)
843845
TEST_ENTRY (timer_null_callback)
844846
TEST_ENTRY (timer_early_check)
847+
TEST_ENTRY (timer_no_double_call_once)
848+
TEST_ENTRY (timer_no_double_call_nowait)
845849

846850
TEST_ENTRY (idle_starvation)
847851
TEST_ENTRY (idle_check)

test/test-timer.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ static int twice_close_cb_called = 0;
3030
static int repeat_cb_called = 0;
3131
static int repeat_close_cb_called = 0;
3232
static int order_cb_called = 0;
33+
static int timer_check_double_call_called = 0;
3334
static uint64_t start_time;
3435
static uv_timer_t tiny_timer;
3536
static uv_timer_t huge_timer1;
@@ -368,3 +369,41 @@ TEST_IMPL(timer_early_check) {
368369
MAKE_VALGRIND_HAPPY(uv_default_loop());
369370
return 0;
370371
}
372+
373+
static void timer_check_double_call(uv_timer_t* handle) {
374+
timer_check_double_call_called++;
375+
}
376+
377+
TEST_IMPL(timer_no_double_call_once) {
378+
uv_timer_t timer_handle;
379+
const uint64_t timeout_ms = 10;
380+
381+
ASSERT_EQ(0, uv_timer_init(uv_default_loop(), &timer_handle));
382+
ASSERT_EQ(0, uv_timer_start(&timer_handle,
383+
timer_check_double_call,
384+
timeout_ms,
385+
timeout_ms));
386+
uv_sleep(timeout_ms * 2);
387+
ASSERT_EQ(1, uv_run(uv_default_loop(), UV_RUN_ONCE));
388+
ASSERT_EQ(1, timer_check_double_call_called);
389+
390+
MAKE_VALGRIND_HAPPY(uv_default_loop());
391+
return 0;
392+
}
393+
394+
TEST_IMPL(timer_no_double_call_nowait) {
395+
uv_timer_t timer_handle;
396+
const uint64_t timeout_ms = 10;
397+
398+
ASSERT_EQ(0, uv_timer_init(uv_default_loop(), &timer_handle));
399+
ASSERT_EQ(0, uv_timer_start(&timer_handle,
400+
timer_check_double_call,
401+
timeout_ms,
402+
timeout_ms));
403+
uv_sleep(timeout_ms * 2);
404+
ASSERT_EQ(1, uv_run(uv_default_loop(), UV_RUN_NOWAIT));
405+
ASSERT_EQ(1, timer_check_double_call_called);
406+
407+
MAKE_VALGRIND_HAPPY(uv_default_loop());
408+
return 0;
409+
}

0 commit comments

Comments
 (0)