libuv/src/win/cares.c

290 lines
9.6 KiB
C

/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <assert.h>
#include "uv.h"
#include "../uv-common.h"
#include "internal.h"
/*
* Subclass of uv_handle_t. Used for integration of c-ares.
*/
struct uv_ares_action_s {
UV_HANDLE_FIELDS
struct uv_req_s ares_req;
SOCKET sock;
int read;
int write;
};
/* default timeout per socket request if ares does not specify value */
/* use 20 sec */
#define ARES_TIMEOUT_MS 20000
/* thread pool callback when socket is signalled */
static void CALLBACK uv_ares_socksignal_tp(void* parameter,
BOOLEAN timerfired) {
WSANETWORKEVENTS network_events;
uv_ares_task_t* sockhandle;
uv_ares_action_t* selhandle;
uv_req_t* uv_ares_req;
uv_loop_t* loop;
assert(parameter != NULL);
if (parameter != NULL) {
sockhandle = (uv_ares_task_t*) parameter;
loop = sockhandle->loop;
/* clear socket status for this event */
/* do not fail if error, thread may run after socket close */
/* The code assumes that c-ares will write all pending data in the */
/* callback, unless the socket would block. We can clear the state here */
/* to avoid unecessary signals. */
WSAEnumNetworkEvents(sockhandle->sock,
sockhandle->h_event,
&network_events);
/* setup new handle */
selhandle = (uv_ares_action_t*)malloc(sizeof(uv_ares_action_t));
if (selhandle == NULL) {
uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
}
selhandle->type = UV_ARES_EVENT;
selhandle->close_cb = NULL;
selhandle->data = sockhandle->data;
selhandle->sock = sockhandle->sock;
selhandle->read =
(network_events.lNetworkEvents & (FD_READ | FD_CONNECT)) ? 1 : 0;
selhandle->write =
(network_events.lNetworkEvents & (FD_WRITE | FD_CONNECT)) ? 1 : 0;
uv_ares_req = &selhandle->ares_req;
uv_req_init(loop, uv_ares_req);
uv_ares_req->type = UV_ARES_EVENT_REQ;
uv_ares_req->data = selhandle;
/* post ares needs to called */
POST_COMPLETION_FOR_REQ(loop, uv_ares_req);
}
}
/* periodically call ares to check for timeouts */
static void uv_ares_poll(uv_timer_t* handle, int status) {
uv_loop_t* loop = handle->loop;
if (loop->ares_chan != NULL && loop->ares_active_sockets > 0) {
ares_process_fd(loop->ares_chan, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
}
}
/* callback from ares when socket operation is started */
static void uv_ares_sockstate_cb(void *data, ares_socket_t sock, int read,
int write) {
/* look to see if we have a handle for this socket in our list */
uv_loop_t* loop = (uv_loop_t*) data;
uv_ares_task_t* uv_handle_ares = uv_find_ares_handle(loop, sock);
int timeoutms = 0;
if (read == 0 && write == 0) {
/* if read and write are 0, cleanup existing data */
/* The code assumes that c-ares does a callback with read = 0 and */
/* write = 0 when the socket is closed. After we recieve this we stop */
/* monitoring the socket. */
if (uv_handle_ares != NULL) {
uv_req_t* uv_ares_req;
uv_handle_ares->h_close_event = CreateEvent(NULL, FALSE, FALSE, NULL);
/* remove Wait */
if (uv_handle_ares->h_wait) {
UnregisterWaitEx(uv_handle_ares->h_wait,
uv_handle_ares->h_close_event);
uv_handle_ares->h_wait = NULL;
}
/* detach socket from the event */
WSAEventSelect(sock, NULL, 0);
if (uv_handle_ares->h_event != WSA_INVALID_EVENT) {
WSACloseEvent(uv_handle_ares->h_event);
uv_handle_ares->h_event = WSA_INVALID_EVENT;
}
/* remove handle from list */
uv_remove_ares_handle(uv_handle_ares);
/* Post request to cleanup the Task */
uv_ares_req = &uv_handle_ares->ares_req;
uv_req_init(loop, uv_ares_req);
uv_ares_req->type = UV_ARES_CLEANUP_REQ;
uv_ares_req->data = uv_handle_ares;
/* post ares done with socket - finish cleanup when all threads done. */
POST_COMPLETION_FOR_REQ(loop, uv_ares_req);
} else {
assert(0);
uv_fatal_error(ERROR_INVALID_DATA, "ares_SockStateCB");
}
} else {
if (uv_handle_ares == NULL) {
/* setup new handle */
/* The code assumes that c-ares will call us when it has an open socket.
We need to call into c-ares when there is something to read,
or when it becomes writable. */
uv_handle_ares = (uv_ares_task_t*)malloc(sizeof(uv_ares_task_t));
if (uv_handle_ares == NULL) {
uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
}
uv_handle_ares->type = UV_ARES_TASK;
uv_handle_ares->close_cb = NULL;
uv_handle_ares->data = loop;
uv_handle_ares->sock = sock;
uv_handle_ares->h_wait = NULL;
uv_handle_ares->flags = 0;
/* create an event to wait on socket signal */
uv_handle_ares->h_event = WSACreateEvent();
if (uv_handle_ares->h_event == WSA_INVALID_EVENT) {
uv_fatal_error(WSAGetLastError(), "WSACreateEvent");
}
/* tie event to socket */
if (SOCKET_ERROR == WSAEventSelect(sock,
uv_handle_ares->h_event,
FD_READ | FD_WRITE | FD_CONNECT)) {
uv_fatal_error(WSAGetLastError(), "WSAEventSelect");
}
/* add handle to list */
uv_add_ares_handle(loop, uv_handle_ares);
uv_ref(loop);
/*
* we have a single polling timer for all ares sockets.
* This is preferred to using ares_timeout. See ares_timeout.c warning.
* if timer is not running start it, and keep socket count
*/
if (loop->ares_active_sockets == 0) {
uv_timer_init(loop, &loop->ares_polling_timer);
uv_timer_start(&loop->ares_polling_timer, uv_ares_poll, 1000L, 1000L);
}
loop->ares_active_sockets++;
/* specify thread pool function to call when event is signaled */
if (RegisterWaitForSingleObject(&uv_handle_ares->h_wait,
uv_handle_ares->h_event,
uv_ares_socksignal_tp,
(void*)uv_handle_ares,
INFINITE,
WT_EXECUTEINWAITTHREAD) == 0) {
uv_fatal_error(GetLastError(), "RegisterWaitForSingleObject");
}
} else {
/* found existing handle. */
assert(uv_handle_ares->type == UV_ARES_TASK);
assert(uv_handle_ares->data != NULL);
assert(uv_handle_ares->h_event != WSA_INVALID_EVENT);
}
}
}
/* called via uv_poll when ares completion port signaled */
void uv_process_ares_event_req(uv_loop_t* loop, uv_ares_action_t* handle,
uv_req_t* req) {
ares_process_fd(loop->ares_chan,
handle->read ? handle->sock : INVALID_SOCKET,
handle->write ? handle->sock : INVALID_SOCKET);
/* release handle for select here */
free(handle);
}
/* called via uv_poll when ares is finished with socket */
void uv_process_ares_cleanup_req(uv_loop_t* loop, uv_ares_task_t* handle,
uv_req_t* req) {
/* check for event complete without waiting */
unsigned int signaled = WaitForSingleObject(handle->h_close_event, 0);
if (signaled != WAIT_TIMEOUT) {
uv_unref(loop);
/* close event handle and free uv handle memory */
CloseHandle(handle->h_close_event);
free(handle);
/* decrement active count. if it becomes 0 stop polling */
if (loop->ares_active_sockets > 0) {
loop->ares_active_sockets--;
if (loop->ares_active_sockets == 0) {
uv_close((uv_handle_t*) &loop->ares_polling_timer, NULL);
}
}
} else {
/* stil busy - repost and try again */
POST_COMPLETION_FOR_REQ(loop, req);
}
}
/* set ares SOCK_STATE callback to our handler */
int uv_ares_init_options(uv_loop_t* loop,
ares_channel *channelptr,
struct ares_options *options,
int optmask) {
int rc;
/* only allow single init at a time */
if (loop->ares_chan != NULL) {
return UV_EALREADY;
}
/* set our callback as an option */
options->sock_state_cb = uv_ares_sockstate_cb;
options->sock_state_cb_data = loop;
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) {
loop->ares_chan = *channelptr;
}
return rc;
}
/* release memory */
void uv_ares_destroy(uv_loop_t* loop, ares_channel channel) {
/* only allow destroy if did init */
if (loop->ares_chan != NULL) {
ares_destroy(channel);
loop->ares_chan = NULL;
}
}