[MEDIUM] implement a monotonic internal clock

If the system date is set backwards while haproxy is running,
some scheduled events are delayed by the amount of time the
clock went backwards. This is particularly problematic on
systems where the date is set at boot, because it seldom
happens that health-checks do not get sent for a few hours.

Before switching to use clock_gettime() on systems which
provide it, we can at least ensure that the clock is not
going backwards and maintain two clocks : the "date" which
represents what the user wants to see (mostly for logs),
and an internal date stored in "now", used for scheduled
events.
This commit is contained in:
Willy Tarreau 2008-06-22 17:18:02 +02:00
parent e5c5ce970f
commit b7f694f20e
15 changed files with 65 additions and 34 deletions

View File

@ -2,7 +2,7 @@
include/common/time.h
Time calculation functions and macros.
Copyright (C) 2000-2007 Willy Tarreau - w@1wt.eu
Copyright (C) 2000-2008 Willy Tarreau - w@1wt.eu
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
@ -49,7 +49,8 @@
#define MINTIME(old, new) (((new)<0)?(old):(((old)<0||(new)<(old))?(new):(old)))
#define SETNOW(a) (*a=now)
extern struct timeval now; /* the current date at any moment */
extern struct timeval now; /* internal date is a monotonic function of real clock */
extern struct timeval date; /* the real current date */
extern struct timeval start_date; /* the process's start date */
@ -83,13 +84,19 @@ REGPRM1 static inline struct timeval *tv_now(struct timeval *tv)
return tv;
}
/* tv_now_mono: sets <date> to the current time (wall clock), <mono> to a value
* following a monotonic function, and applies any required correction if the
* time goes backwards. Note that while we could improve it a bit by checking
* that the new date is not too far in the future, it is not much necessary to
* do so.
*/
REGPRM2 struct timeval *tv_now_mono(struct timeval *mono, struct timeval *wall);
/*
* sets a struct timeval to its highest value so that it can never happen
* note that only tv_usec is necessary to detect it since a tv_usec > 999999
* is normally not possible.
*
*/
REGPRM1 static inline struct timeval *tv_eternity(struct timeval *tv)
{
tv->tv_sec = tv->tv_usec = TV_ETERNITY;

View File

@ -117,7 +117,8 @@ struct session {
struct http_txn txn; /* current HTTP transaction being processed. Should become a list. */
struct {
int logwait; /* log fields waiting to be collected : LW_* */
struct timeval tv_accept; /* date of the accept() (beginning of the session) */
struct timeval accept_date; /* date of the accept() in user date */
struct timeval tv_accept; /* date of the accept() in internal date (monotonic) */
struct timeval tv_request; /* date the request arrives, {0,0} if never occurs */
long t_queue; /* delay before the session gets out of the connect queue, -1 if never occurs */
long t_connect; /* delay before the connect() to the server succeeds, -1 if never occurs */

View File

@ -2831,7 +2831,7 @@ int readcfgfile(const char *file)
*/
/* will be needed further to delay some tasks */
tv_now(&now);
tv_now_mono(&now, &date);
if ((curproxy = proxy) == NULL) {
Alert("parsing %s : no <listen> line. Nothing to do !\n",

View File

@ -356,7 +356,7 @@ static int event_srv_chk_w(int fd)
if (s->proxy->options & PR_O_SSL3_CHK) {
/* SSL requires that we put Unix time in the request */
int gmt_time = htonl(now.tv_sec);
int gmt_time = htonl(date.tv_sec);
memcpy(s->proxy->check_req + 11, &gmt_time, 4);
}

View File

@ -1,7 +1,7 @@
/*
* Client-side variables and functions.
*
* Copyright 2000-2007 Willy Tarreau <w@1wt.eu>
* Copyright 2000-2008 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -201,7 +201,8 @@ int event_accept(int fd) {
else
s->logs.logwait = p->to_log;
s->logs.tv_accept = now;
s->logs.accept_date = date; /* user-visible date for logging */
s->logs.tv_accept = now; /* corrected date for internal use */
tv_zero(&s->logs.tv_request);
s->logs.t_queue = -1;
s->logs.t_connect = -1;

View File

@ -1,7 +1,7 @@
/*
* FD polling functions for linux epoll()
*
* Copyright 2000-2007 Willy Tarreau <w@1wt.eu>
* Copyright 2000-2008 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -243,7 +243,7 @@ REGPRM2 static void _do_poll(struct poller *p, struct timeval *exp)
fd = MIN(maxfd, global.tune.maxpollevents);
status = epoll_wait(epoll_fd, epoll_events, fd, wait_time);
tv_now(&now);
tv_now_mono(&now, &date);
for (count = 0; count < status; count++) {
fd = epoll_events[count].data.fd;

View File

@ -1,7 +1,7 @@
/*
* FD polling functions for FreeBSD kqueue()
*
* Copyright 2000-2007 Willy Tarreau <w@1wt.eu>
* Copyright 2000-2008 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -130,7 +130,7 @@ REGPRM2 static void _do_poll(struct poller *p, struct timeval *exp)
kev, // struct kevent *eventlist
fd, // int nevents
to_ptr); // const struct timespec *timeout
tv_now(&now);
tv_now_mono(&now, &date);
for (count = 0; count < status; count++) {
fd = kev[count].ident;

View File

@ -1,7 +1,7 @@
/*
* FD polling functions for generic poll()
*
* Copyright 2000-2007 Willy Tarreau <w@1wt.eu>
* Copyright 2000-2008 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -134,7 +134,7 @@ REGPRM2 static void _do_poll(struct poller *p, struct timeval *exp)
wait_time = __tv_ms_elapsed(&now, exp) + 1;
status = poll(poll_events, nbfd, wait_time);
tv_now(&now);
tv_now_mono(&now, &date);
for (count = 0; status > 0 && count < nbfd; count++) {
fd = poll_events[count].fd;

View File

@ -1,7 +1,7 @@
/*
* FD polling functions for generic select()
*
* Copyright 2000-2007 Willy Tarreau <w@1wt.eu>
* Copyright 2000-2008 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -124,7 +124,7 @@ REGPRM2 static void _do_poll(struct poller *p, struct timeval *exp)
NULL,
tv_isset(exp) ? &delta : NULL);
tv_now(&now);
tv_now_mono(&now, &date);
if (status <= 0)
return;

View File

@ -418,7 +418,7 @@ REGPRM2 static void _do_poll(struct poller *p, struct timeval *exp)
* returning now without checking epoll_wait().
*/
if (++last_skipped <= 1) {
tv_now(&now);
tv_now_mono(&now, &date);
return;
}
}
@ -452,7 +452,7 @@ REGPRM2 static void _do_poll(struct poller *p, struct timeval *exp)
spec_processed = 0;
status = epoll_wait(epoll_fd, epoll_events, fd, wait_time);
tv_now(&now);
tv_now_mono(&now, &date);
for (count = 0; count < status; count++) {
int e = epoll_events[count].events;

View File

@ -415,7 +415,7 @@ void init(int argc, char **argv)
global.rlimit_memmax = HAPROXY_MEMMAX;
#endif
tv_now(&now);
tv_now_mono(&now, &date);
start_date = now;
init_task();
@ -897,7 +897,7 @@ void run_poll_loop()
{
struct timeval next;
tv_now(&now);
tv_now_mono(&now, &date);
while (1) {
process_runnable_tasks(&next);

View File

@ -1,7 +1,7 @@
/*
* General logging functions.
*
* Copyright 2000-2006 Willy Tarreau <w@1wt.eu>
* Copyright 2000-2008 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -70,7 +70,7 @@ void Alert(const char *fmt, ...)
if (!(global.mode & MODE_QUIET) || (global.mode & (MODE_VERBOSE | MODE_STARTING))) {
va_start(argp, fmt);
get_localtime(now.tv_sec, &tm);
get_localtime(date.tv_sec, &tm);
fprintf(stderr, "[ALERT] %03d/%02d%02d%02d (%d) : ",
tm.tm_yday, tm.tm_hour, tm.tm_min, tm.tm_sec, (int)getpid());
vfprintf(stderr, fmt, argp);
@ -91,7 +91,7 @@ void Warning(const char *fmt, ...)
if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) {
va_start(argp, fmt);
get_localtime(now.tv_sec, &tm);
get_localtime(date.tv_sec, &tm);
fprintf(stderr, "[WARNING] %03d/%02d%02d%02d (%d) : ",
tm.tm_yday, tm.tm_hour, tm.tm_min, tm.tm_sec, (int)getpid());
vfprintf(stderr, fmt, argp);
@ -185,11 +185,11 @@ void send_log(struct proxy *p, int level, const char *message, ...)
if (level < 0 || progname == NULL || message == NULL)
return;
if (now.tv_sec != tvsec || dataptr == NULL) {
if (unlikely(date.tv_sec != tvsec || dataptr == NULL)) {
/* this string is rebuild only once a second */
struct tm tm;
tvsec = now.tv_sec;
tvsec = date.tv_sec;
get_localtime(tvsec, &tm);
hdr_len = snprintf(logmsg, sizeof(logmsg),

View File

@ -772,7 +772,7 @@ static void http_sess_log(struct session *s)
(const void *)&((struct sockaddr_in6 *)(&s->cli_addr))->sin6_addr,
pn, sizeof(pn));
get_localtime(s->logs.tv_accept.tv_sec, &tm);
get_localtime(s->logs.accept_date.tv_sec, &tm);
/* FIXME: let's limit ourselves to frontend logging for now. */
tolog = fe->to_log;
@ -835,7 +835,7 @@ static void http_sess_log(struct session *s)
ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port) :
ntohs(((struct sockaddr_in6 *)&s->cli_addr)->sin6_port),
tm.tm_mday, monthname[tm.tm_mon], tm.tm_year+1900,
tm.tm_hour, tm.tm_min, tm.tm_sec, s->logs.tv_accept.tv_usec/1000,
tm.tm_hour, tm.tm_min, tm.tm_sec, s->logs.accept_date.tv_usec/1000,
fe->id, be->id, svid,
t_request,
(s->logs.t_queue >= 0) ? s->logs.t_queue - t_request : -1,

View File

@ -1,7 +1,7 @@
/*
* Proxy variables and functions.
*
* Copyright 2000-2007 Willy Tarreau <w@1wt.eu>
* Copyright 2000-2008 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -385,7 +385,7 @@ void soft_stop(void)
stopping = 1;
p = proxy;
tv_now(&now); /* else, the old time before select will be used */
tv_now_mono(&now, &date); /* else, the old time before select will be used */
while (p) {
if (p->state != PR_STSTOPPED) {
Warning("Stopping proxy %s in %d ms.\n", p->id, p->grace);
@ -434,7 +434,7 @@ void pause_proxies(void)
err = 0;
p = proxy;
tv_now(&now); /* else, the old time before select will be used */
tv_now_mono(&now, &date); /* else, the old time before select will be used */
while (p) {
if (p->state != PR_STERROR &&
p->state != PR_STSTOPPED &&
@ -469,7 +469,7 @@ void listen_proxies(void)
struct listener *l;
p = proxy;
tv_now(&now); /* else, the old time before select will be used */
tv_now_mono(&now, &date); /* else, the old time before select will be used */
while (p) {
if (p->state == PR_STPAUSED) {
Warning("Enabling proxy %s.\n", p->id);

View File

@ -1,7 +1,7 @@
/*
* Time calculation functions.
*
* Copyright 2000-2007 Willy Tarreau <w@1wt.eu>
* Copyright 2000-2008 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -16,7 +16,8 @@
#include <common/standard.h>
#include <common/time.h>
struct timeval now; /* the current date at any moment */
struct timeval now; /* internal date is a monotonic function of real clock */
struct timeval date; /* the real current date */
struct timeval start_date; /* the process's start date */
/*
@ -142,6 +143,27 @@ REGPRM2 int _tv_isgt(const struct timeval *tv1, const struct timeval *tv2)
return __tv_isgt(tv1, tv2);
}
/* tv_now_mono: sets <date> to the current time (wall clock), <mono> to a value
* following a monotonic function, and applies any required correction if the
* time goes backwards. Note that while we could improve it a bit by checking
* that the new date is not too far in the future, it is not much necessary to
* do so.
*/
REGPRM2 struct timeval *tv_now_mono(struct timeval *mono, struct timeval *wall)
{
static struct timeval tv_offset;
struct timeval adjusted;
gettimeofday(wall, NULL);
__tv_add(&adjusted, wall, &tv_offset);
if (unlikely(__tv_islt(&adjusted, mono))) {
__tv_remain(wall, mono, &tv_offset);
return mono;
}
*mono = adjusted;
return mono;
}
char *human_time(int t, short hz_div) {
static char rv[sizeof("24855d23h")+1]; // longest of "23h59m" and "59m59s"
char *p = rv;