/** * \file libuv.c * \brief Eventloop extension for libuv. * */ /* * Copyright (c) 2013, NLNet Labs, Verisign, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the names of the copyright holders nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "debug.h" #include "types-internal.h" #include #include "getdns/getdns_ext_libuv.h" #define UV_DEBUG 0 #if defined(UV_DEBUG) && UV_DEBUG #include #define DEBUG_UV(...) DEBUG_ON(__VA_ARGS__) #else #define DEBUG_UV(...) DEBUG_OFF(__VA_ARGS__) #endif typedef struct getdns_libuv { getdns_eventloop_vmt *vmt; uv_loop_t *loop; struct mem_funcs mf; } getdns_libuv; static void getdns_libuv_run(getdns_eventloop *loop) { (void) uv_run(((getdns_libuv *)loop)->loop, UV_RUN_DEFAULT); } static void getdns_libuv_run_once(getdns_eventloop *loop, int blocking) { (void) uv_run(((getdns_libuv *)loop)->loop, blocking ? UV_RUN_ONCE : UV_RUN_NOWAIT); } static void getdns_libuv_cleanup(getdns_eventloop *loop) { getdns_libuv *ext = (getdns_libuv *)loop; GETDNS_FREE(ext->mf, ext); } typedef struct poll_timer { uv_poll_t poll; uv_timer_t timer; int to_close; struct mem_funcs mf; } poll_timer; static void getdns_libuv_close_cb(uv_handle_t *handle) { poll_timer *my_ev = (poll_timer *)handle->data; DEBUG_UV("enter libuv_close_cb(el_ev->ev = %p, to_close = %d)\n" , my_ev, my_ev->to_close); if (--my_ev->to_close) { DEBUG_UV( "exit libuv_close_cb(el_ev->ev = %p, to_close = %d)\n" , my_ev, my_ev->to_close); return; } DEBUG_UV("enter libuv_close_cb to_free: %p\n", my_ev); GETDNS_FREE(my_ev->mf, my_ev); DEBUG_UV("enter libuv_close_cb freed: %p\n", my_ev); } static getdns_return_t getdns_libuv_clear(getdns_eventloop *loop, getdns_eventloop_event *el_ev) { poll_timer *my_ev = (poll_timer *)el_ev->ev; uv_poll_t *my_poll; uv_timer_t *my_timer; (void)loop; /* unused parameter */ assert(my_ev); DEBUG_UV("enter libuv_clear(el_ev = %p, my_ev = %p, to_close = %d)\n" , el_ev, my_ev, my_ev->to_close); if (el_ev->read_cb || el_ev->write_cb) { my_poll = &my_ev->poll; uv_poll_stop(my_poll); my_ev->to_close += 1; my_poll->data = my_ev; uv_close((uv_handle_t *)my_poll, getdns_libuv_close_cb); } if (el_ev->timeout_cb) { my_timer = &my_ev->timer; uv_timer_stop(my_timer); my_ev->to_close += 1; my_timer->data = my_ev; uv_close((uv_handle_t *)my_timer, getdns_libuv_close_cb); } el_ev->ev = NULL; DEBUG_UV("exit libuv_clear(el_ev = %p, my_ev = %p, to_close = %d)\n" , el_ev, my_ev, my_ev->to_close); return GETDNS_RETURN_GOOD; } static void getdns_libuv_cb(uv_poll_t *poll, int status, int events) { getdns_eventloop_event *el_ev = (getdns_eventloop_event *)poll->data; if (status == 0) { if (events & UV_READABLE) { assert(el_ev->read_cb); DEBUG_UV("enter libuv_read_cb(el_ev = %p, el_ev->ev = %p)\n" , el_ev, el_ev->ev); el_ev->read_cb(el_ev->userarg); DEBUG_UV("exit libuv_read_cb(el_ev = %p, el_ev->ev = %p)\n" , el_ev, el_ev->ev); } else if (events & UV_WRITABLE) { assert(el_ev->write_cb); DEBUG_UV("enter libuv_write_cb(el_ev = %p, el_ev->ev = %p)\n" , el_ev, el_ev->ev); el_ev->write_cb(el_ev->userarg); DEBUG_UV("exit libuv_write_cb(el_ev = %p, el_ev->ev = %p)\n" , el_ev, el_ev->ev); } else { assert(ASSERT_UNREACHABLE); } } } static void #ifdef HAVE_NEW_UV_TIMER_CB getdns_libuv_timeout_cb(uv_timer_t *timer) #else getdns_libuv_timeout_cb(uv_timer_t *timer, int status) #endif { getdns_eventloop_event *el_ev = (getdns_eventloop_event *)timer->data; #ifndef HAVE_NEW_UV_TIMER_CB (void)status; /* unused parameter */ #endif assert(el_ev->timeout_cb); DEBUG_UV("enter libuv_timeout_cb(el_ev = %p, el_ev->ev = %p)\n" , el_ev, el_ev->ev); el_ev->timeout_cb(el_ev->userarg); DEBUG_UV("exit libuv_timeout_cb(el_ev = %p, el_ev->ev = %p)\n" , el_ev, el_ev->ev); } static getdns_return_t getdns_libuv_schedule(getdns_eventloop *loop, int fd, uint64_t timeout, getdns_eventloop_event *el_ev) { getdns_libuv *ext = (getdns_libuv *)loop; poll_timer *my_ev; uv_poll_t *my_poll; uv_timer_t *my_timer; assert(el_ev); assert(!(el_ev->read_cb || el_ev->write_cb) || fd >= 0); assert( el_ev->read_cb || el_ev->write_cb || el_ev->timeout_cb); DEBUG_UV("enter libuv_schedule(el_ev = %p, el_ev->ev = %p)\n" , el_ev, el_ev->ev); if (!(my_ev = GETDNS_MALLOC(ext->mf, poll_timer))) return GETDNS_RETURN_MEMORY_ERROR; my_ev->to_close = 0; my_ev->mf = ext->mf; el_ev->ev = my_ev; if (el_ev->read_cb || el_ev->write_cb) { my_poll = &my_ev->poll; my_poll->data = el_ev; uv_poll_init(ext->loop, my_poll, fd); int events = (el_ev->read_cb ? UV_READABLE : 0) | (el_ev->write_cb ? UV_WRITABLE : 0); uv_poll_start(my_poll, events, getdns_libuv_cb); } if (el_ev->timeout_cb) { my_timer = &my_ev->timer; my_timer->data = el_ev; uv_timer_init(ext->loop, my_timer); uv_timer_start(my_timer, getdns_libuv_timeout_cb, timeout, 0); } DEBUG_UV("exit libuv_schedule(el_ev = %p, el_ev->ev = %p)\n" , el_ev, el_ev->ev); return GETDNS_RETURN_GOOD; } getdns_return_t getdns_extension_set_libuv_loop(getdns_context *context, uv_loop_t *loop) { static getdns_eventloop_vmt getdns_libuv_vmt = { getdns_libuv_cleanup, getdns_libuv_schedule, getdns_libuv_clear, getdns_libuv_run, getdns_libuv_run_once }; getdns_libuv *ext; if (!context) return GETDNS_RETURN_BAD_CONTEXT; if (!loop) return GETDNS_RETURN_INVALID_PARAMETER; ext = GETDNS_MALLOC(*priv_getdns_context_mf(context), getdns_libuv); if (!ext) return GETDNS_RETURN_MEMORY_ERROR; ext->vmt = &getdns_libuv_vmt; ext->loop = loop; ext->mf = *priv_getdns_context_mf(context); return getdns_context_set_eventloop(context, (getdns_eventloop *)ext); }