diff --git a/ares/CHANGES b/ares/CHANGES index 3d44b190a..db3d035fa 100644 --- a/ares/CHANGES +++ b/ares/CHANGES @@ -1,5 +1,10 @@ Changelog for the c-ares project +* May 13 2008 (Daniel Stenberg) + +- Introducing millisecond resolution support for the timeout option. See + ares_init_options()'s ARES_OPT_TIMEOUTMS. + * May 9 2008 (Yang Tse) - Use monotonic time source if available, for private function ares__tvnow() diff --git a/ares/ares.h b/ares/ares.h index 640101990..3609f8676 100644 --- a/ares/ares.h +++ b/ares/ares.h @@ -1,6 +1,7 @@ /* $Id$ */ /* Copyright 1998 by the Massachusetts Institute of Technology. + * Copyright (C) 2007 by Daniel Stenberg * * Permission to use, copy, modify, and distribute this * software and its documentation for any purpose and without @@ -112,6 +113,7 @@ extern "C" { #define ARES_OPT_SORTLIST (1 << 10) #define ARES_OPT_SOCK_SNDBUF (1 << 11) #define ARES_OPT_SOCK_RCVBUF (1 << 12) +#define ARES_OPT_TIMEOUTMS (1 << 13) /* Nameinfo flag values */ #define ARES_NI_NOFQDN (1 << 0) @@ -179,7 +181,7 @@ struct apattern; struct ares_options { int flags; - int timeout; + int timeout; /* in seconds or milliseconds, depending on options */ int tries; int ndots; unsigned short udp_port; diff --git a/ares/ares_init.3 b/ares/ares_init.3 index 026d8da0e..428cb03cc 100644 --- a/ares/ares_init.3 +++ b/ares/ares_init.3 @@ -1,6 +1,7 @@ .\" $Id$ .\" .\" Copyright 1998 by the Massachusetts Institute of Technology. +.\" Copyright (C) 2007-2008 by Daniel Stenberg .\" .\" Permission to use, copy, modify, and distribute this .\" software and its documentation for any purpose and without @@ -14,7 +15,7 @@ .\" this software for any purpose. It is provided "as is" .\" without express or implied warranty. .\" -.TH ARES_INIT 3 "7 December 2004" +.TH ARES_INIT 3 "13 May 2008" .SH NAME ares_init, ares_init_options \- Initialize a resolver channel .SH SYNOPSIS @@ -49,10 +50,22 @@ description of possible flag values. .B ARES_OPT_TIMEOUT .B int \fItimeout\fP; .br -The number of seconds each name server is given to respond to a query -on the first try. (After the first try, the timeout algorithm becomes -more complicated, but scales linearly with the value of -\fItimeout\fP.) The default is five seconds. +The number of seconds each name server is given to respond to a query on the +first try. (After the first try, the timeout algorithm becomes more +complicated, but scales linearly with the value of \fItimeout\fP.) The +default is five seconds. This option is being deprecated by +\fIARES_OPT_TIMEOUTMS\fP starting in c-ares 1.5.2. +.TP 18 +.B ARES_OPT_TIMEOUTMS +.B int \fItimeout\fP; +.br +The number of milliseconds each name server is given to respond to a query on +the first try. (After the first try, the timeout algorithm becomes more +complicated, but scales linearly with the value of \fItimeout\fP.) The +default is five seconds. Note that this option is specified with the same +struct field as the former \fIARES_OPT_TIMEOUT\fP, it is but the option bits +that tell c-ares how to interpret the number. This option was added in c-ares +1.5.2. .TP 18 .B ARES_OPT_TRIES .B int \fItries\fP; diff --git a/ares/ares_init.c b/ares/ares_init.c index 9b655a867..278181362 100644 --- a/ares/ares_init.c +++ b/ares/ares_init.c @@ -1,6 +1,7 @@ /* $Id$ */ /* Copyright 1998 by the Massachusetts Institute of Technology. + * Copyright (C) 2007-2008 by Daniel Stenberg * * Permission to use, copy, modify, and distribute this * software and its documentation for any purpose and without @@ -254,13 +255,16 @@ int ares_save_options(ares_channel channel, struct ares_options *options, if (!ARES_CONFIG_CHECK(channel)) return ARES_ENODATA; - (*optmask) = (ARES_OPT_FLAGS|ARES_OPT_TIMEOUT|ARES_OPT_TRIES|ARES_OPT_NDOTS| + (*optmask) = (ARES_OPT_FLAGS|ARES_OPT_TRIES|ARES_OPT_NDOTS| ARES_OPT_UDP_PORT|ARES_OPT_TCP_PORT|ARES_OPT_SOCK_STATE_CB| ARES_OPT_SERVERS|ARES_OPT_DOMAINS|ARES_OPT_LOOKUPS| - ARES_OPT_SORTLIST); + ARES_OPT_SORTLIST|ARES_OPT_TIMEOUTMS); /* Copy easy stuff */ options->flags = channel->flags; + + /* We return full millisecond resolution but that's only because we don't + set the ARES_OPT_TIMEOUT anymore, only the new ARES_OPT_TIMEOUTMS */ options->timeout = channel->timeout; options->tries = channel->tries; options->ndots = channel->ndots; @@ -328,8 +332,10 @@ static int init_by_options(ares_channel channel, /* Easy stuff. */ if ((optmask & ARES_OPT_FLAGS) && channel->flags == -1) channel->flags = options->flags; - if ((optmask & ARES_OPT_TIMEOUT) && channel->timeout == -1) + if ((optmask & ARES_OPT_TIMEOUTMS) && channel->timeout == -1) channel->timeout = options->timeout; + else if ((optmask & ARES_OPT_TIMEOUT) && channel->timeout == -1) + channel->timeout = options->timeout * 1000; if ((optmask & ARES_OPT_TRIES) && channel->tries == -1) channel->tries = options->tries; if ((optmask & ARES_OPT_NDOTS) && channel->ndots == -1) diff --git a/ares/ares_private.h b/ares/ares_private.h index 9354e6300..6ec4dc47a 100644 --- a/ares/ares_private.h +++ b/ares/ares_private.h @@ -4,6 +4,7 @@ /* $Id$ */ /* Copyright 1998 by the Massachusetts Institute of Technology. + * Copyright (C) 2004-2008 by Daniel Stenberg * * Permission to use, copy, modify, and distribute this * software and its documentation for any purpose and without @@ -48,7 +49,7 @@ #include #endif -#define DEFAULT_TIMEOUT 5 +#define DEFAULT_TIMEOUT 5000 /* milliseconds */ #define DEFAULT_TRIES 4 #ifndef INADDR_NONE #define INADDR_NONE 0xffffffff @@ -149,7 +150,7 @@ struct server_state { struct query { /* Query ID from qbuf, for faster lookup, and current timeout */ unsigned short qid; - time_t timeout; + struct timeval timeout; /* * Links for the doubly-linked lists in which we insert a query. @@ -217,7 +218,7 @@ typedef struct rc4_key struct ares_channeldata { /* Configuration data */ int flags; - int timeout; + int timeout; /* in milliseconds */ int tries; int ndots; int udp_port; @@ -242,7 +243,8 @@ struct ares_channeldata { /* Generation number to use for the next TCP socket open/close */ int tcp_connection_generation; - /* The time at which we last called process_timeouts() */ + /* The time at which we last called process_timeouts(). Uses integer seconds + just to draw the line somewhere. */ time_t last_timeout_processed; /* Circular, doubly-linked list of queries, bucketed various ways.... */ @@ -259,8 +261,18 @@ struct ares_channeldata { void *sock_state_cb_data; }; +/* return true if now is exactly check time or later */ +int ares__timedout(struct timeval *now, + struct timeval *check); +/* add the specific number of milliseconds to the time in the first argument */ +int ares__timeadd(struct timeval *now, + int millisecs); +/* return time offset between now and (future) check, in milliseconds */ +int ares__timeoffset(struct timeval *now, + struct timeval *check); void ares__rc4(rc4_key* key,unsigned char *buffer_ptr, int buffer_len); -void ares__send_query(ares_channel channel, struct query *query, time_t now); +void ares__send_query(ares_channel channel, struct query *query, + struct timeval *now); void ares__close_sockets(ares_channel channel, struct server_state *server); int ares__get_hostent(FILE *fp, int family, struct hostent **host); int ares__read_line(FILE *fp, char **buf, int *bufsize); diff --git a/ares/ares_process.c b/ares/ares_process.c index 442e3280c..eca363aec 100644 --- a/ares/ares_process.c +++ b/ares/ares_process.c @@ -1,6 +1,7 @@ /* $Id$ */ /* Copyright 1998 by the Massachusetts Institute of Technology. + * Copyright (C) 2004-2008 by Daniel Stenberg * * Permission to use, copy, modify, and distribute this * software and its documentation for any purpose and without @@ -42,6 +43,9 @@ #ifdef HAVE_ARPA_NAMESER_COMPAT_H #include #endif +#ifdef HAVE_SYS_TIME_H +#include +#endif #endif /* WIN32 && !WATT32 */ #ifdef HAVE_STRINGS_H @@ -71,21 +75,25 @@ static int try_again(int errnum); static void write_tcp_data(ares_channel channel, fd_set *write_fds, - ares_socket_t write_fd, time_t now); + ares_socket_t write_fd, struct timeval *now); static void read_tcp_data(ares_channel channel, fd_set *read_fds, - ares_socket_t read_fd, time_t now); + ares_socket_t read_fd, struct timeval *now); static void read_udp_packets(ares_channel channel, fd_set *read_fds, - ares_socket_t read_fd, time_t now); + ares_socket_t read_fd, struct timeval *now); static void advance_tcp_send_queue(ares_channel channel, int whichserver, ssize_t num_bytes); -static void process_timeouts(ares_channel channel, time_t now); -static void process_broken_connections(ares_channel channel, time_t now); +static void process_timeouts(ares_channel channel, struct timeval *now); +static void process_broken_connections(ares_channel channel, + struct timeval *now); static void process_answer(ares_channel channel, unsigned char *abuf, - int alen, int whichserver, int tcp, time_t now); -static void handle_error(ares_channel channel, int whichserver, time_t now); + int alen, int whichserver, int tcp, + struct timeval *now); +static void handle_error(ares_channel channel, int whichserver, + struct timeval *now); static void skip_server(ares_channel channel, struct query *query, int whichserver); -static void next_server(ares_channel channel, struct query *query, time_t now); +static void next_server(ares_channel channel, struct query *query, + struct timeval *now); static int configure_socket(int s, ares_channel channel); static int open_tcp_socket(ares_channel channel, struct server_state *server); static int open_udp_socket(ares_channel channel, struct server_state *server); @@ -94,19 +102,59 @@ static int same_questions(const unsigned char *qbuf, int qlen, static void end_query(ares_channel channel, struct query *query, int status, unsigned char *abuf, int alen); +/* return true if now is exactly check time or later */ +int ares__timedout(struct timeval *now, + struct timeval *check) +{ + int secs = (now->tv_sec - check->tv_sec); + + if(secs > 0) + return 1; /* yes, timed out */ + if(secs < -1) + return 0; /* nope, not timed out */ + + /* if the full seconds were identical, check the sub second parts */ + return (now->tv_usec - check->tv_usec >= 0); +} + +/* add the specific number of milliseconds to the time in the first argument */ +int ares__timeadd(struct timeval *now, + int millisecs) +{ + now->tv_sec += millisecs/1000; + now->tv_usec += (millisecs%1000)*1000; + + if(now->tv_usec >= 1000000) { + ++(now->tv_sec); + now->tv_usec -= 1000000; + } + + return 0; +} + +/* return time offset between now and (future) check, in milliseconds */ +int ares__timeoffset(struct timeval *now, + struct timeval *check) +{ + int secs = (check->tv_sec - now->tv_sec); /* this many seconds */ + int us = (check->tv_usec - now->tv_usec); /* this many microseconds */ + + return secs*1000 + us/1000; /* return them combined as milliseconds */ +} + + /* Something interesting happened on the wire, or there was a timeout. * See what's up and respond accordingly. */ void ares_process(ares_channel channel, fd_set *read_fds, fd_set *write_fds) { - time_t now; + struct timeval now = ares__tvnow(); - time(&now); - write_tcp_data(channel, write_fds, ARES_SOCKET_BAD, now); - read_tcp_data(channel, read_fds, ARES_SOCKET_BAD, now); - read_udp_packets(channel, read_fds, ARES_SOCKET_BAD, now); - process_timeouts(channel, now); - process_broken_connections(channel, now); + write_tcp_data(channel, write_fds, ARES_SOCKET_BAD, &now); + read_tcp_data(channel, read_fds, ARES_SOCKET_BAD, &now); + read_udp_packets(channel, read_fds, ARES_SOCKET_BAD, &now); + process_timeouts(channel, &now); + process_broken_connections(channel, &now); } /* Something interesting happened on the wire, or there was a timeout. @@ -117,13 +165,12 @@ void ares_process_fd(ares_channel channel, file descriptors */ ares_socket_t write_fd) { - time_t now; + struct timeval now = ares__tvnow(); - time(&now); - write_tcp_data(channel, NULL, write_fd, now); - read_tcp_data(channel, NULL, read_fd, now); - read_udp_packets(channel, NULL, read_fd, now); - process_timeouts(channel, now); + write_tcp_data(channel, NULL, write_fd, &now); + read_tcp_data(channel, NULL, read_fd, &now); + read_udp_packets(channel, NULL, read_fd, &now); + process_timeouts(channel, &now); } @@ -158,7 +205,7 @@ static int try_again(int errnum) static void write_tcp_data(ares_channel channel, fd_set *write_fds, ares_socket_t write_fd, - time_t now) + struct timeval *now) { struct server_state *server; struct send_request *sendreq; @@ -177,7 +224,8 @@ static void write_tcp_data(ares_channel channel, /* Make sure server has data to send and is selected in write_fds or write_fd. */ server = &channel->servers[i]; - if (!server->qhead || server->tcp_socket == ARES_SOCKET_BAD || server->is_broken) + if (!server->qhead || server->tcp_socket == ARES_SOCKET_BAD || + server->is_broken) continue; if(write_fds) { @@ -281,7 +329,7 @@ static void advance_tcp_send_queue(ares_channel channel, int whichserver, * a packet if we finish reading one. */ static void read_tcp_data(ares_channel channel, fd_set *read_fds, - ares_socket_t read_fd, time_t now) + ares_socket_t read_fd, struct timeval *now) { struct server_state *server; int i; @@ -377,7 +425,7 @@ static void read_tcp_data(ares_channel channel, fd_set *read_fds, /* If any UDP sockets select true for reading, process them. */ static void read_udp_packets(ares_channel channel, fd_set *read_fds, - ares_socket_t read_fd, time_t now) + ares_socket_t read_fd, struct timeval *now) { struct server_state *server; int i; @@ -428,7 +476,7 @@ static void read_udp_packets(ares_channel channel, fd_set *read_fds, } /* If any queries have timed out, note the timeout and move them on. */ -static void process_timeouts(ares_channel channel, time_t now) +static void process_timeouts(ares_channel channel, struct timeval *now) { time_t t; /* the time of the timeouts we're processing */ struct query *query; @@ -441,14 +489,14 @@ static void process_timeouts(ares_channel channel, time_t now) * only a handful of requests that fall into the "now" bucket, so * this should be quite quick. */ - for (t = channel->last_timeout_processed; t <= now; t++) + for (t = channel->last_timeout_processed; t <= now->tv_sec; t++) { list_head = &(channel->queries_by_timeout[t % ARES_TIMEOUT_TABLE_SIZE]); for (list_node = list_head->next; list_node != list_head; ) { query = list_node->data; list_node = list_node->next; /* in case the query gets deleted */ - if (query->timeout != 0 && now >= query->timeout) + if (query->timeout.tv_sec && ares__timedout(now, &query->timeout)) { query->error_status = ARES_ETIMEOUT; ++query->timeouts; @@ -456,12 +504,13 @@ static void process_timeouts(ares_channel channel, time_t now) } } } - channel->last_timeout_processed = now; + channel->last_timeout_processed = now->tv_sec; } /* Handle an answer from a server. */ static void process_answer(ares_channel channel, unsigned char *abuf, - int alen, int whichserver, int tcp, time_t now) + int alen, int whichserver, int tcp, + struct timeval *now) { int tc, rcode; unsigned short id; @@ -539,7 +588,8 @@ static void process_answer(ares_channel channel, unsigned char *abuf, } /* Close all the connections that are no longer usable. */ -static void process_broken_connections(ares_channel channel, time_t now) +static void process_broken_connections(ares_channel channel, + struct timeval *now) { int i; for (i = 0; i < channel->nservers; i++) @@ -552,7 +602,8 @@ static void process_broken_connections(ares_channel channel, time_t now) } } -static void handle_error(ares_channel channel, int whichserver, time_t now) +static void handle_error(ares_channel channel, int whichserver, + struct timeval *now) { struct server_state *server; struct query *query; @@ -603,7 +654,8 @@ static void skip_server(ares_channel channel, struct query *query, } } -static void next_server(ares_channel channel, struct query *query, time_t now) +static void next_server(ares_channel channel, struct query *query, + struct timeval *now) { /* Advance to the next server or try. */ query->server++; @@ -640,7 +692,8 @@ static void next_server(ares_channel channel, struct query *query, time_t now) end_query(channel, query, query->error_status, NULL, 0); } -void ares__send_query(ares_channel channel, struct query *query, time_t now) +void ares__send_query(ares_channel channel, struct query *query, + struct timeval *now) { struct send_request *sendreq; struct server_state *server; @@ -707,16 +760,17 @@ void ares__send_query(ares_channel channel, struct query *query, time_t now) return; } } - query->timeout = now - + ((query->try == 0) ? channel->timeout - : channel->timeout << query->try / channel->nservers); + query->timeout = *now; + ares__timeadd(&query->timeout, + (query->try == 0) ? channel->timeout + : channel->timeout << query->try / channel->nservers); /* Keep track of queries bucketed by timeout, so we can process * timeout events quickly. */ ares__remove_from_list(&(query->queries_by_timeout)); ares__insert_in_list( &(query->queries_by_timeout), - &(channel->queries_by_timeout[query->timeout % + &(channel->queries_by_timeout[query->timeout.tv_sec % ARES_TIMEOUT_TABLE_SIZE])); /* Keep track of queries bucketed by server, so we can process server diff --git a/ares/ares_send.c b/ares/ares_send.c index 2007321f9..fe921b625 100644 --- a/ares/ares_send.c +++ b/ares/ares_send.c @@ -39,7 +39,7 @@ void ares_send(ares_channel channel, const unsigned char *qbuf, int qlen, { struct query *query; int i; - time_t now; + struct timeval now; /* Verify that the query is at least long enough to hold the header. */ if (qlen < HFIXEDSZ || qlen >= (1 << 16)) @@ -74,7 +74,7 @@ void ares_send(ares_channel channel, const unsigned char *qbuf, int qlen, /* Compute the query ID. Start with no timeout. */ query->qid = (unsigned short)DNS_HEADER_QID(qbuf); - query->timeout = 0; + query->timeout.tv_sec = query->timeout.tv_usec = 0; /* Form the TCP query buffer by prepending qlen (as two * network-order bytes) to qbuf. @@ -107,17 +107,17 @@ void ares_send(ares_channel channel, const unsigned char *qbuf, int qlen, ares__init_list_node(&(query->queries_by_timeout), query); ares__init_list_node(&(query->queries_to_server), query); ares__init_list_node(&(query->all_queries), query); - + /* Chain the query into the list of all queries. */ ares__insert_in_list(&(query->all_queries), &(channel->all_queries)); /* Keep track of queries bucketed by qid, so we can process DNS * responses quickly. */ ares__insert_in_list( - &(query->queries_by_qid), - &(channel->queries_by_qid[query->qid % ARES_QID_TABLE_SIZE])); + &(query->queries_by_qid), + &(channel->queries_by_qid[query->qid % ARES_QID_TABLE_SIZE])); /* Perform the first query action. */ - time(&now); - ares__send_query(channel, query, now); + now = ares__tvnow(); + ares__send_query(channel, query, &now); } diff --git a/ares/ares_timeout.c b/ares/ares_timeout.c index 12b93b044..ce9849153 100644 --- a/ares/ares_timeout.c +++ b/ares/ares_timeout.c @@ -37,16 +37,16 @@ struct timeval *ares_timeout(ares_channel channel, struct timeval *maxtv, struct query *query; struct list_node* list_head; struct list_node* list_node; - time_t now; - time_t offset, min_offset; /* these use time_t since some 32 bit systems - still use 64 bit time_t! (like VS2005) */ + struct timeval now; + struct timeval nextstop; + long offset, min_offset; /* No queries, no timeout (and no fetch of the current time). */ if (ares__is_list_empty(&(channel->all_queries))) return maxtv; /* Find the minimum timeout for the current set of queries. */ - time(&now); + now = ares__tvnow(); min_offset = -1; list_head = &(channel->all_queries); @@ -54,23 +54,26 @@ struct timeval *ares_timeout(ares_channel channel, struct timeval *maxtv, list_node = list_node->next) { query = list_node->data; - if (query->timeout == 0) + if (query->timeout.tv_sec == 0) continue; - offset = query->timeout - now; + offset = ares__timeoffset(&now, &query->timeout); if (offset < 0) offset = 0; if (min_offset == -1 || offset < min_offset) min_offset = offset; } - /* If we found a minimum timeout and it's sooner than the one - * specified in maxtv (if any), return it. Otherwise go with - * maxtv. + if(min_offset != -1) { + nextstop = now; + ares__timeadd(&now, min_offset); + } + + /* If we found a minimum timeout and it's sooner than the one specified in + * maxtv (if any), return it. Otherwise go with maxtv. */ - if (min_offset != -1 && (!maxtv || min_offset <= maxtv->tv_sec)) + if (min_offset != -1 && (!maxtv || ares__timedout(maxtv, &nextstop))) { - tvbuf->tv_sec = (long)min_offset; - tvbuf->tv_usec = 0; + *tvbuf = nextstop; return tvbuf; } else