diff --git a/test/test-gethostbyname.c b/test/test-gethostbyname.c index 41ee766b..570476d3 100644 --- a/test/test-gethostbyname.c +++ b/test/test-gethostbyname.c @@ -35,41 +35,35 @@ int bynamecallbacksig; int ares_byaddrcallbacks; int byaddrcallbacksig; -static uv_buf_t alloc_cb(uv_handle_t* handle, size_t size) { - uv_buf_t buf; - buf.base = (char*)malloc(size); - buf.len = size; - return buf; -} - static void aresbynamecallback( void *arg, int status, int timeouts, struct hostent *hostent) { - int * iargs; - ASSERT(arg != NULL); - iargs = (int*)arg; - ASSERT(*iargs == bynamecallbacksig); - ASSERT(timeouts == 0); + int * iargs; + ASSERT(arg != NULL); + iargs = (int*)arg; + ASSERT(*iargs == bynamecallbacksig); + ASSERT(timeouts == 0); - ares_bynamecallbacks++; + printf("aresbynamecallback %d\n", ares_bynamecallbacks++); } + static void aresbyaddrcallback( void *arg, int status, int timeouts, struct hostent *hostent) { - int * iargs; - ASSERT(arg != NULL); - iargs = (int*)arg; - ASSERT(*iargs == byaddrcallbacksig); - ASSERT(timeouts == 0); + int * iargs; + ASSERT(arg != NULL); + iargs = (int*)arg; + ASSERT(*iargs == byaddrcallbacksig); + ASSERT(timeouts == 0); - ares_byaddrcallbacks++; + printf("aresbyaddrcallback %d\n", ares_byaddrcallbacks++); } -static void prep_tcploopback() -{ + +static void prep_tcploopback() { /* for test, use echo server - TCP port TEST_PORT on loopback */ struct sockaddr_in test_server = uv_ip4_addr("127.0.0.1", 0); int rc; @@ -97,7 +91,7 @@ TEST_IMPL(gethostbyname) { return 1; } - uv_init(alloc_cb); + uv_init(); printf("Start basic gethostbyname test\n"); prep_tcploopback(); diff --git a/uv-unix.c b/uv-unix.c index 60f28964..5e21dfcc 100644 --- a/uv-unix.c +++ b/uv-unix.c @@ -19,6 +19,7 @@ */ #include "uv.h" +#include "uv-common.h" #include /* NULL */ #include /* printf */ @@ -44,6 +45,18 @@ static uv_err_t last_err; +struct uv_ares_data_s { + ares_channel channel; + /* + * While the channel is active this timer is called once per second to be sure + * that we're always calling ares_process. See the warning above the + * definition of ares_timeout(). + */ + ev_timer timer; +}; + +static struct uv_ares_data_s ares_data; + void uv__tcp_io(EV_P_ ev_io* watcher, int revents); void uv__next(EV_P_ ev_idle* watcher, int revents); @@ -67,6 +80,30 @@ void uv_flag_set(uv_handle_t* handle, int flag) { } +/* TODO Share this code with Windows. */ +/* TODO Expose callback to user to handle fatal error like V8 does. */ +static void uv_fatal_error(const int errorno, const char* syscall) { + char* buf = NULL; + const char* errmsg; + + if (buf) { + errmsg = buf; + } else { + errmsg = "Unknown error"; + } + + if (syscall) { + fprintf(stderr, "\nlibuv fatal error. %s: (%d) %s\n", syscall, errorno, + errmsg); + } else { + fprintf(stderr, "\nlibuv fatal error. (%d) %s\n", errorno, errmsg); + } + + *((char*)NULL) = 0xff; /* Force debug break */ + abort(); +} + + uv_err_t uv_last_error() { return last_err; } @@ -1303,17 +1340,145 @@ int64_t uv_timer_get_repeat(uv_timer_t* timer) { return (int64_t)(1000 * timer->timer_watcher.repeat); } -/* c-ares integration initialize and terminate */ -int uv_ares_init_options(ares_channel *channelptr, - struct ares_options *options, - int optmask) { - int r; - r = ares_init_options(channelptr, options, optmask); - return r; + +/* + * This is called once per second by ares_data.timer. It is used to + * constantly callback into c-ares for possibly processing timeouts. + */ +static void uv__ares_timeout(EV_P_ struct ev_timer* watcher, int revents) { + assert(watcher == &ares_data.timer); + assert(revents == EV_TIMER); + assert(!uv_ares_handles_empty()); + ares_process_fd(ares_data.channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD); } + +static void uv__ares_io(EV_P_ struct ev_io* watcher, int revents) { + /* Reset the idle timer */ + ev_timer_again(EV_A_ &ares_data.timer); + + /* Process DNS responses */ + ares_process_fd(ares_data.channel, + revents & EV_READ ? watcher->fd : ARES_SOCKET_BAD, + revents & EV_WRITE ? watcher->fd : ARES_SOCKET_BAD); +} + + +/* Allocates and returns a new uv_ares_task_t */ +static uv_ares_task_t* uv__ares_task_create(int fd) { + uv_ares_task_t* h = malloc(sizeof(uv_ares_task_t)); + + if (h == NULL) { + uv_fatal_error(ENOMEM, "malloc"); + } + + h->sock = fd; + + ev_io_init(&h->read_watcher, uv__ares_io, fd, EV_READ); + ev_io_init(&h->write_watcher, uv__ares_io, fd, EV_WRITE); + + h->read_watcher.data = h; + h->write_watcher.data = h; +} + + +/* Callback from ares when socket operation is started */ +static void uv__ares_sockstate_cb(void* data, ares_socket_t sock, + int read, int write) { + uv_ares_task_t* h = uv_find_ares_handle(sock); + + if (read || write) { + if (!h) { + /* New socket */ + + /* If this is the first socket then start the timer. */ + if (!ev_is_active(&ares_data.timer)) { + assert(uv_ares_handles_empty()); + ev_timer_again(EV_DEFAULT_UC_ &ares_data.timer); + } + + h = uv__ares_task_create(sock); + uv_add_ares_handle(h); + } + + if (read) { + ev_io_start(EV_DEFAULT_UC_ &h->read_watcher); + } else { + ev_io_stop(EV_DEFAULT_UC_ &h->read_watcher); + } + + if (write) { + ev_io_start(EV_DEFAULT_UC_ &h->write_watcher); + } else { + ev_io_stop(EV_DEFAULT_UC_ &h->write_watcher); + } + + } else { + /* + * read == 0 and write == 0 this is c-ares's way of notifying us that + * the socket is now closed. We must free the data associated with + * socket. + */ + assert(h && "When an ares socket is closed we should have a handle for it"); + + ev_io_stop(EV_DEFAULT_UC_ &h->read_watcher); + ev_io_stop(EV_DEFAULT_UC_ &h->write_watcher); + + uv_remove_ares_handle(h); + free(h); + + if (uv_ares_handles_empty()) { + ev_timer_stop(EV_DEFAULT_UC_ &ares_data.timer); + } + } +} + + +/* c-ares integration initialize and terminate */ +/* TODO: share this with windows? */ +int uv_ares_init_options(ares_channel *channelptr, + struct ares_options *options, + int optmask) { + int rc; + + /* only allow single init at a time */ + if (ares_data.channel != NULL) { + uv_err_new_artificial(NULL, UV_EALREADY); + return -1; + } + + /* set our callback as an option */ + options->sock_state_cb = uv__ares_sockstate_cb; + options->sock_state_cb_data = &ares_data; + optmask |= ARES_OPT_SOCK_STATE_CB; + + /* We do the call to ares_init_option for caller. */ + rc = ares_init_options(channelptr, options, optmask); + + /* if success, save channel */ + if (rc == ARES_SUCCESS) { + ares_data.channel = *channelptr; + } + + /* + * Initialize the timeout timer. The timer won't be started until the + * first socket is opened. + */ + ev_init(&ares_data.timer, uv__ares_timeout); + ares_data.timer.repeat = 1.0; + + return rc; +} + + +/* TODO share this with windows? */ void uv_ares_destroy(ares_channel channel) { - ares_destroy(channel); + /* only allow destroy if did init */ + if (ares_data.channel != NULL) { + ev_timer_stop(EV_DEFAULT_UC_ &ares_data.timer); + ares_destroy(channel); + ares_data.channel = NULL; + } } diff --git a/uv-unix.h b/uv-unix.h index 13305abb..f1eb773d 100644 --- a/uv-unix.h +++ b/uv-unix.h @@ -102,7 +102,10 @@ typedef struct { #define UV_ARES_ACTION_PRIVATE_FIELDS /* TODO */ -#define UV_ARES_TASK_PRIVATE_FIELDS /* TODO */ +#define UV_ARES_TASK_PRIVATE_FIELDS \ + int sock; \ + ev_io read_watcher; \ + ev_io write_watcher; #define UV_GETADDRINFO_PRIVATE_FIELDS /* TODO */