evdns: New flag to make evdns not prevent the event loop from exiting

Here is the brief description of problem:
When you are use evdns to resolve domains to IP adresses (see
./sample/dns-example) you loop never returns from event_base_dispatch(),
and because of this the program will never terminated.

Because existing programs may be depending on the old behavior, we
only apply the fix when evdns_base_new() is created with a new flag -
EVDNS_BASE_DISABLE_WHEN_INACTIVE.

 (Commit message edited by Nick while squashing the branch.)
This commit is contained in:
Azat Khuzhin 2013-03-27 20:15:46 +04:00 committed by Nick Mathewson
parent 2fad0f3d52
commit 6b7fa620e8
3 changed files with 52 additions and 6 deletions

47
evdns.c
View File

@ -236,6 +236,10 @@ struct nameserver {
char choked; /* true if we have an EAGAIN from this server's socket */
char write_waiting; /* true if we are waiting for EV_WRITE events */
struct evdns_base *base;
/* Number of currently inflight requests: used
* to track when we should add/del the event. */
int requests_inflight;
};
@ -358,6 +362,8 @@ struct evdns_base {
#ifndef EVENT__DISABLE_THREAD_SUPPORT
void *lock;
#endif
int disable_when_inactive;
};
struct hosts_entry {
@ -660,12 +666,19 @@ request_finished(struct request *const req, struct request **head, int free_hand
if (was_inflight) {
evtimer_del(&req->timeout_event);
base->global_requests_inflight--;
req->ns->requests_inflight--;
} else {
base->global_requests_waiting--;
}
/* it was initialized during request_new / evtimer_assign */
event_debug_unassign(&req->timeout_event);
if (req->ns &&
req->ns->requests_inflight == 0 &&
req->base->disable_when_inactive) {
event_del(&req->ns->event);
}
if (!req->request_appended) {
/* need to free the request data on it's own */
mm_free(req->request);
@ -744,6 +757,8 @@ evdns_requests_pump_waiting_queue(struct evdns_base *base) {
base->global_requests_inflight++;
req->ns = nameserver_pick(base);
req->ns->requests_inflight++;
request_trans_id_set(req, transaction_id_pick(base));
evdns_request_insert(req, &REQ_HEAD(base, req->trans_id));
@ -2188,6 +2203,13 @@ evdns_request_transmit_to(struct request *req, struct nameserver *server) {
int r;
ASSERT_LOCKED(req->base);
ASSERT_VALID_REQUEST(req);
if (server->requests_inflight == 1 &&
req->base->disable_when_inactive &&
event_add(&server->event, NULL) < 0) {
return 1;
}
r = sendto(server->socket, (void*)req->request, req->request_len, 0,
(struct sockaddr *)&server->address, server->addrlen);
if (r < 0) {
@ -2496,8 +2518,9 @@ evdns_nameserver_add_impl_(struct evdns_base *base, const struct sockaddr *addre
memcpy(&ns->address, address, addrlen);
ns->addrlen = addrlen;
ns->state = 1;
event_assign(&ns->event, ns->base->event_base, ns->socket, EV_READ | EV_PERSIST, nameserver_ready_callback, ns);
if (event_add(&ns->event, NULL) < 0) {
event_assign(&ns->event, ns->base->event_base, ns->socket,
EV_READ | EV_PERSIST, nameserver_ready_callback, ns);
if (!base->disable_when_inactive && event_add(&ns->event, NULL) < 0) {
err = 2;
goto out2;
}
@ -2768,7 +2791,10 @@ request_submit(struct request *const req) {
/* if it has a nameserver assigned then this is going */
/* straight into the inflight queue */
evdns_request_insert(req, &REQ_HEAD(base, req->trans_id));
base->global_requests_inflight++;
req->ns->requests_inflight++;
evdns_request_transmit(req);
} else {
evdns_request_insert(req, &base->req_waiting_head);
@ -3829,7 +3855,7 @@ evdns_config_windows_nameservers(void)
#endif
struct evdns_base *
evdns_base_new(struct event_base *event_base, int initialize_nameservers)
evdns_base_new(struct event_base *event_base, int flags)
{
struct evdns_base *base;
@ -3877,7 +3903,16 @@ evdns_base_new(struct event_base *event_base, int initialize_nameservers)
TAILQ_INIT(&base->hostsdb);
if (initialize_nameservers) {
#define EVDNS_BASE_ALL_FLAGS (0x8001)
if (flags & ~EVDNS_BASE_ALL_FLAGS) {
flags = EVDNS_BASE_INITIALIZE_NAMESERVERS;
log(EVDNS_LOG_WARN,
"Unrecognized flag passed to evdns_base_new(). Assuming "
"you meant EVDNS_BASE_INITIALIZE_NAMESERVERS.");
}
#undef EVDNS_BASE_ALL_FLAGS
if (flags & EVDNS_BASE_INITIALIZE_NAMESERVERS) {
int r;
#ifdef _WIN32
r = evdns_base_config_windows_nameservers(base);
@ -3889,6 +3924,10 @@ evdns_base_new(struct event_base *event_base, int initialize_nameservers)
return NULL;
}
}
if (flags & EVDNS_BASE_DISABLE_WHEN_INACTIVE) {
base->disable_when_inactive = 1;
}
EVDNS_UNLOCK(base);
return base;
}

View File

@ -201,6 +201,12 @@ typedef void (*evdns_callback_type) (int result, char type, int count, int ttl,
struct evdns_base;
struct event_base;
/** Flag for evdns_base_new: process resolv.conf. */
#define EVDNS_BASE_INITIALIZE_NAMESERVERS 1
/** Flag for evdns_base_new: Do not prevent the libevent event loop from
* exiting when we have no active dns requests. */
#define EVDNS_BASE_DISABLE_WHEN_INACTIVE 0x8000
/**
Initialize the asynchronous DNS library.
@ -209,7 +215,8 @@ struct event_base;
evdns_config_windows_nameservers() on Windows.
@param event_base the event base to associate the dns client with
@param initialize_nameservers 1 if resolve.conf processing should occur
@param flags any of EVDNS_BASE_INITIALIZE_NAMESERVERS|
EVDNS_BASE_DISABLE_WHEN_INACTIVE
@return evdns_base object if successful, or NULL if an error occurred.
@see evdns_base_free()
*/

View File

@ -179,7 +179,7 @@ main(int c, char **v) {
#endif
event_base = event_base_new();
evdns_base = evdns_base_new(event_base, 0);
evdns_base = evdns_base_new(event_base, EVDNS_BASE_DISABLE_WHEN_INACTIVE);
evdns_set_log_fn(logfn);
if (servertest) {