From: Timo Sirainen Date: Mon, 1 Mar 2021 11:03:16 +0000 (+0200) Subject: lib: ioloop - Fix 0 ms timeouts to trigger without waiting 2ms between calls X-Git-Tag: 2.3.15~135 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=fac27f192d8432c45d360025613f7d432271c5bb;p=thirdparty%2Fdovecot%2Fcore.git lib: ioloop - Fix 0 ms timeouts to trigger without waiting 2ms between calls --- diff --git a/src/lib/ioloop.c b/src/lib/ioloop.c index b3e8ecc2e3..f3be87480d 100644 --- a/src/lib/ioloop.c +++ b/src/lib/ioloop.c @@ -419,21 +419,19 @@ timeout_reset_timeval(struct timeout *timeout, struct timeval *tv_now) return; timeout_update_next(timeout, tv_now); - if (timeout->msecs <= 1) { - /* if we came here from io_loop_handle_timeouts(), - next_run must be larger than tv_now or we could go to - infinite loop. +1000 to get 1 ms further, another +1000 to - account for timeout_update_next()'s truncation. */ - timeout->next_run.tv_usec += 2000; - if (timeout->next_run.tv_usec >= 1000000) { - timeout->next_run.tv_sec++; - timeout->next_run.tv_usec -= 1000000; - } + /* If we came here from io_loop_handle_timeouts_real(), next_run must + be larger than tv_now or it can go to infinite loop. This would + mainly happen with 0 ms timeouts. Avoid this by making sure + next_run is at least 1 us higher than tv_now. + + Note that some callers (like master process's process_min_avail + preforking timeout) really do want the 0 ms timeout to trigger + multiple times as rapidly as it can (but in separate ioloop runs). + So don't increase it more than by 1 us. */ + if (tv_now != NULL && timeval_cmp(&timeout->next_run, tv_now) <= 0) { + timeout->next_run = *tv_now; + timeval_add_usecs(&timeout->next_run, 1); } - i_assert(tv_now == NULL || - timeout->next_run.tv_sec > tv_now->tv_sec || - (timeout->next_run.tv_sec == tv_now->tv_sec && - timeout->next_run.tv_usec > tv_now->tv_usec)); priorityq_remove(timeout->ioloop->timeouts, &timeout->item); priorityq_add(timeout->ioloop->timeouts, &timeout->item); } @@ -445,7 +443,7 @@ void timeout_reset(struct timeout *timeout) } static int timeout_get_wait_time(struct timeout *timeout, struct timeval *tv_r, - struct timeval *tv_now) + struct timeval *tv_now, bool in_timeout_loop) { int ret; @@ -470,6 +468,11 @@ static int timeout_get_wait_time(struct timeout *timeout, struct timeval *tv_r, tv_r->tv_usec = 0; return 0; } + if (tv_r->tv_sec == 0 && tv_r->tv_usec == 1 && !in_timeout_loop) { + /* Possibly 0 ms timeout. Don't wait for a full millisecond + for it to trigger. */ + return 0; + } if (tv_r->tv_sec > INT_MAX/1000-1) tv_r->tv_sec = INT_MAX/1000-1; @@ -509,7 +512,7 @@ static int io_loop_get_wait_time(struct ioloop *ioloop, struct timeval *tv_r) tv_r->tv_usec = 0; } else { tv_now.tv_sec = 0; - msecs = timeout_get_wait_time(timeout, tv_r, &tv_now); + msecs = timeout_get_wait_time(timeout, tv_r, &tv_now, FALSE); } ioloop->next_max_time = tv_now; timeval_add_msecs(&ioloop->next_max_time, msecs); @@ -519,6 +522,7 @@ static int io_loop_get_wait_time(struct ioloop *ioloop, struct timeval *tv_r) ioloop and after that we update ioloop_timeval immediately again. */ ioloop_timeval = tv_now; ioloop_time = tv_now.tv_sec; + i_assert(msecs == 0 || timeout->msecs > 0 || timeout->one_shot); return msecs; } @@ -663,7 +667,7 @@ static void io_loop_handle_timeouts_real(struct ioloop *ioloop) /* use tv_call to make sure we don't get to infinite loop in case callbacks update ioloop_timeval. */ - if (timeout_get_wait_time(timeout, &tv, &tv_call) > 0) + if (timeout_get_wait_time(timeout, &tv, &tv_call, TRUE) > 0) break; if (timeout->one_shot) { diff --git a/src/lib/test-ioloop.c b/src/lib/test-ioloop.c index 792d15a30a..57fdacd3e5 100644 --- a/src/lib/test-ioloop.c +++ b/src/lib/test-ioloop.c @@ -125,6 +125,49 @@ static void test_ioloop_timeout(void) test_end(); } +static void zero_timeout_callback(unsigned int *counter) +{ + *counter += 1; +} + +static void test_ioloop_zero_timeout(void) +{ + struct ioloop *ioloop; + struct timeout *to; + struct io *io; + unsigned int counter = 0; + int fd[2]; + + test_begin("ioloop zero timeout"); + + if (pipe(fd) < 0) + i_fatal("pipe() failed: %m"); + switch (fork()) { + case (pid_t)-1: + i_fatal("fork() failed: %m"); + case 0: + sleep(1); + char c = 0; + if (write(fd[1], &c, 1) < 0) + i_fatal("write(pipe) failed: %m"); + test_exit(0); + default: + break; + } + + ioloop = io_loop_create(); + to = timeout_add_short(0, zero_timeout_callback, &counter); + io = io_add(fd[0], IO_READ, io_loop_stop, ioloop); + + io_loop_run(ioloop); + test_assert_ucmp(counter, >, 1000); + + timeout_remove(&to); + io_remove(&io); + io_loop_destroy(&ioloop); + test_end(); +} + static void io_callback(void *context ATTR_UNUSED) { } @@ -197,6 +240,7 @@ static void test_ioloop_pending_io(void) void test_ioloop(void) { test_ioloop_timeout(); + test_ioloop_zero_timeout(); test_ioloop_find_fd_conditions(); test_ioloop_pending_io(); test_ioloop_fd();