From 6795f542ed816a3c977085d4f74df1d62a37b690 Mon Sep 17 00:00:00 2001 From: Timo Sirainen Date: Sun, 21 Jun 2009 21:44:54 -0400 Subject: [PATCH] ioloop: Added callback for handling time jumping forwards/backwards. The default implementation is now to only log a warning when time moves backwards. The callback is also called if it's detected that time jumps forwards. In both cases existing timeouts' run times are updated so that they're called approximately the intended time. --HG-- branch : HEAD --- src/lib/ioloop-epoll.c | 2 +- src/lib/ioloop-internal.h | 6 +- src/lib/ioloop-kqueue.c | 2 +- src/lib/ioloop-poll.c | 2 +- src/lib/ioloop-select.c | 2 +- src/lib/ioloop.c | 118 +++++++++++++++++++++++--------------- src/lib/ioloop.h | 5 ++ 7 files changed, 85 insertions(+), 52 deletions(-) diff --git a/src/lib/ioloop-epoll.c b/src/lib/ioloop-epoll.c index adda4a1c97..e93295cee9 100644 --- a/src/lib/ioloop-epoll.c +++ b/src/lib/ioloop-epoll.c @@ -169,7 +169,7 @@ void io_loop_handler_run(struct ioloop *ioloop) bool call; /* get the time left for next timeout task */ - msecs = io_loop_get_wait_time(ioloop, &tv, NULL); + msecs = io_loop_get_wait_time(ioloop, &tv); events = array_get_modifiable(&ctx->events, &events_count); ret = epoll_wait(ctx->epfd, events, events_count, msecs); diff --git a/src/lib/ioloop-internal.h b/src/lib/ioloop-internal.h index c7b294b21a..9666445ffc 100644 --- a/src/lib/ioloop-internal.h +++ b/src/lib/ioloop-internal.h @@ -19,6 +19,9 @@ struct ioloop { struct ioloop_notify_handler_context *notify_handler_context; unsigned int max_fd_count; + io_loop_time_moved_callback_t *time_moved_callback; + time_t next_max_time; + unsigned int running:1; }; @@ -53,8 +56,7 @@ struct timeout { struct ioloop *ioloop; }; -int io_loop_get_wait_time(struct ioloop *ioloop, struct timeval *tv_r, - struct timeval *tv_now); +int io_loop_get_wait_time(struct ioloop *ioloop, struct timeval *tv_r); void io_loop_handle_timeouts(struct ioloop *ioloop); /* I/O handler calls */ diff --git a/src/lib/ioloop-kqueue.c b/src/lib/ioloop-kqueue.c index fc7c631a3b..ccf00aa8a9 100644 --- a/src/lib/ioloop-kqueue.c +++ b/src/lib/ioloop-kqueue.c @@ -118,7 +118,7 @@ void io_loop_handler_run(struct ioloop *ioloop) int ret, i; /* get the time left for next timeout task */ - io_loop_get_wait_time(ioloop, &tv, NULL); + io_loop_get_wait_time(ioloop, &tv); ts.tv_sec = tv.tv_sec; ts.tv_nsec = tv.tv_usec * 1000; diff --git a/src/lib/ioloop-poll.c b/src/lib/ioloop-poll.c index f1e2bf0dec..c7ae258989 100644 --- a/src/lib/ioloop-poll.c +++ b/src/lib/ioloop-poll.c @@ -152,7 +152,7 @@ void io_loop_handler_run(struct ioloop *ioloop) bool call; /* get the time left for next timeout task */ - msecs = io_loop_get_wait_time(ioloop, &tv, NULL); + msecs = io_loop_get_wait_time(ioloop, &tv); ret = poll(ctx->fds, ctx->fds_pos, msecs); if (ret < 0 && errno != EINTR) diff --git a/src/lib/ioloop-select.c b/src/lib/ioloop-select.c index 2781fa5a6e..4e4d4c9985 100644 --- a/src/lib/ioloop-select.c +++ b/src/lib/ioloop-select.c @@ -112,7 +112,7 @@ void io_loop_handler_run(struct ioloop *ioloop) int ret; /* get the time left for next timeout task */ - io_loop_get_wait_time(ioloop, &tv, NULL); + io_loop_get_wait_time(ioloop, &tv); memcpy(&ctx->tmp_read_fds, &ctx->read_fds, sizeof(fd_set)); memcpy(&ctx->tmp_write_fds, &ctx->write_fds, sizeof(fd_set)); diff --git a/src/lib/ioloop.c b/src/lib/ioloop.c index 90eec6e79e..2fe3248e17 100644 --- a/src/lib/ioloop.c +++ b/src/lib/ioloop.c @@ -5,9 +5,6 @@ #include -/* If time moves backwards more than this, kill ourself instead of sleeping. */ -#define IOLOOP_MAX_TIME_BACKWARDS_SLEEP 5 - #define timer_is_larger(tvp, uvp) \ ((tvp)->tv_sec > (uvp)->tv_sec || \ ((tvp)->tv_sec == (uvp)->tv_sec && \ @@ -193,13 +190,13 @@ static int timeout_get_wait_time(struct timeout *timeout, struct timeval *tv_r, { int ret; - if (tv_now == NULL) { - if (gettimeofday(tv_r, NULL) < 0) + if (tv_now->tv_sec == 0) { + if (gettimeofday(tv_now, NULL) < 0) i_fatal("gettimeofday(): %m"); - } else { - tv_r->tv_sec = tv_now->tv_sec; - tv_r->tv_usec = tv_now->tv_usec; - } + } + tv_r->tv_sec = tv_now->tv_sec; + tv_r->tv_usec = tv_now->tv_usec; + i_assert(tv_r->tv_sec > 0); i_assert(timeout->next_run.tv_sec > 0); @@ -224,11 +221,12 @@ static int timeout_get_wait_time(struct timeout *timeout, struct timeval *tv_r, return ret; } -int io_loop_get_wait_time(struct ioloop *ioloop, struct timeval *tv_r, - struct timeval *tv_now) +int io_loop_get_wait_time(struct ioloop *ioloop, struct timeval *tv_r) { + struct timeval tv_now; struct priorityq_item *item; struct timeout *timeout; + int msecs; item = priorityq_peek(ioloop->timeouts); timeout = (struct timeout *)item; @@ -236,10 +234,14 @@ int io_loop_get_wait_time(struct ioloop *ioloop, struct timeval *tv_r, /* no timeouts. give it INT_MAX msecs. */ tv_r->tv_sec = INT_MAX / 1000; tv_r->tv_usec = 0; + ioloop->next_max_time = (1ULL << (TIME_T_MAX_BITS-1)) - 1; return INT_MAX; } - return timeout_get_wait_time(timeout, tv_r, tv_now); + tv_now.tv_sec = 0; + msecs = timeout_get_wait_time(timeout, tv_r, &tv_now); + ioloop->next_max_time = (tv_now.tv_sec + msecs/1000) + 1; + return msecs; } static int timeout_cmp(const void *p1, const void *p2) @@ -253,50 +255,64 @@ static int timeout_cmp(const void *p1, const void *p2) return diff; } +static void io_loop_default_time_moved(time_t old_time, time_t new_time) +{ + if (old_time > new_time) { + i_warning("Time moved backwards by %ld seconds.", + (long)(old_time - new_time)); + } +} + +static void io_loop_timeouts_update(struct ioloop *ioloop, long diff_secs) +{ + struct priorityq_item *const *items; + unsigned int i, count; + + count = priorityq_count(ioloop->timeouts); + items = priorityq_items(ioloop->timeouts); + for (i = 0; i < count; i++) { + struct timeout *to = (struct timeout *)items[i]; + + to->next_run.tv_sec += diff_secs; + } +} + +static void io_loops_timeouts_update(long diff_secs) +{ + struct ioloop *ioloop; + + for (ioloop = current_ioloop; ioloop != NULL; ioloop = ioloop->prev) + io_loop_timeouts_update(ioloop, diff_secs); +} + static void io_loop_handle_timeouts_real(struct ioloop *ioloop) { struct priorityq_item *item; struct timeval tv, tv_call; - unsigned int t_id; + unsigned int t_id; if (gettimeofday(&ioloop_timeval, NULL) < 0) i_fatal("gettimeofday(): %m"); /* Don't bother comparing usecs. */ - if (ioloop_time > ioloop_timeval.tv_sec) { - time_t diff = ioloop_time - ioloop_timeval.tv_sec; - - /* Note that this code is here only because this is the easiest - place to check for this. The I/O loop code itself could be - easily fixed to work with time moving backwards, but there's - really no point because there are a lot of other places - which may break in more or less bad ways, such as files' - timestamps moving backwards. */ - if (diff > IOLOOP_MAX_TIME_BACKWARDS_SLEEP) { - i_fatal("Time just moved backwards by %ld seconds. " - "This might cause a lot of problems, " - "so I'll just kill myself now. " - "http://wiki.dovecot.org/TimeMovedBackwards", - (long)diff); - } else { - i_error("Time just moved backwards by %ld seconds. " - "I'll sleep now until we're back in present. " - "http://wiki.dovecot.org/TimeMovedBackwards", - (long)diff); - /* Sleep extra second to make sure usecs also grows. */ - diff++; - - while (diff > 0 && sleep(diff) != 0) { - /* don't use sleep()'s return value, because - it could get us to a long loop in case - interrupts just keep coming */ - diff = ioloop_time - time(NULL) + 1; - } - - /* Try again. */ - io_loop_handle_timeouts(ioloop); - } + if (unlikely(ioloop_time > ioloop_timeval.tv_sec)) { + /* time moved backwards */ + io_loops_timeouts_update(-(long)(ioloop_time - + ioloop_timeval.tv_sec)); + ioloop->time_moved_callback(ioloop_time, + ioloop_timeval.tv_sec); + /* the callback may have slept, so check the time again. */ + if (gettimeofday(&ioloop_timeval, NULL) < 0) + i_fatal("gettimeofday(): %m"); + } else if (unlikely(ioloop_timeval.tv_sec > + ioloop->next_max_time)) { + io_loops_timeouts_update(ioloop_timeval.tv_sec - + ioloop->next_max_time); + /* time moved forwards */ + ioloop->time_moved_callback(ioloop->next_max_time, + ioloop_timeval.tv_sec); } + ioloop_time = ioloop_timeval.tv_sec; tv_call = ioloop_timeval; @@ -369,6 +385,10 @@ struct ioloop *io_loop_create(void) ioloop = i_new(struct ioloop, 1); ioloop->timeouts = priorityq_init(timeout_cmp, 32); + ioloop->time_moved_callback = current_ioloop != NULL ? + current_ioloop->time_moved_callback : + io_loop_default_time_moved; + ioloop->prev = current_ioloop; current_ioloop = ioloop; @@ -411,6 +431,12 @@ void io_loop_destroy(struct ioloop **_ioloop) i_free(ioloop); } +void io_loop_set_time_moved_callback(struct ioloop *ioloop, + io_loop_time_moved_callback_t *callback) +{ + ioloop->time_moved_callback = callback; +} + void io_loop_set_current(struct ioloop *ioloop) { current_ioloop = ioloop; diff --git a/src/lib/ioloop.h b/src/lib/ioloop.h index c4d57d4032..fcdb869b01 100644 --- a/src/lib/ioloop.h +++ b/src/lib/ioloop.h @@ -31,6 +31,7 @@ enum io_notify_result { typedef void io_callback_t(void *context); typedef void timeout_callback_t(void *context); +typedef void io_loop_time_moved_callback_t(time_t old_time, time_t new_time); /* Time when the I/O loop started calling handlers. Can be used instead of time(NULL). */ @@ -92,6 +93,10 @@ void io_loop_set_max_fd_count(struct ioloop *ioloop, unsigned int max_fds); /* Destroy I/O loop and set ioloop pointer to NULL. */ void io_loop_destroy(struct ioloop **ioloop); +/* If time moves backwards or jumps forwards call the callback. */ +void io_loop_set_time_moved_callback(struct ioloop *ioloop, + io_loop_time_moved_callback_t *callback); + /* Change the current_ioloop. */ void io_loop_set_current(struct ioloop *ioloop); -- 2.47.3