resolve: cache lookup for async resolvers

While waiting for a host resolve, check if the host cache may have
gotten the name already (by someone else), for when the same name is
resolved by several simultanoues requests.

The resolver thread occasionally gets stuck in getaddrinfo() when the
DNS or anything else is crappy or slow, so when a host is found in the
DNS cache, leave the thread alone and let itself cleanup the mess.
This commit is contained in:
Michael Wallner 2014-08-20 23:31:53 +02:00 committed by Daniel Stenberg
parent 2434a4e88d
commit 09b5a99816
4 changed files with 139 additions and 41 deletions

View File

@ -166,6 +166,7 @@ struct thread_sync_data {
#ifdef HAVE_GETADDRINFO
struct addrinfo hints;
#endif
struct thread_data *td; /* for thread-self cleanup */
};
struct thread_data {
@ -202,13 +203,16 @@ void destroy_thread_sync_data(struct thread_sync_data * tsd)
/* Initialize resolver thread synchronization data */
static
int init_thread_sync_data(struct thread_sync_data * tsd,
int init_thread_sync_data(struct thread_data * td,
const char * hostname,
int port,
const struct addrinfo *hints)
{
struct thread_sync_data *tsd = &td->tsd;
memset(tsd, 0, sizeof(*tsd));
tsd->td = td;
tsd->port = port;
#ifdef HAVE_GETADDRINFO
DEBUGASSERT(hints);
@ -266,6 +270,7 @@ static int getaddrinfo_complete(struct connectdata *conn)
static unsigned int CURL_STDCALL getaddrinfo_thread (void *arg)
{
struct thread_sync_data *tsd = (struct thread_sync_data*)arg;
struct thread_data *td = tsd->td;
char service[12];
int rc;
@ -280,8 +285,16 @@ static unsigned int CURL_STDCALL getaddrinfo_thread (void *arg)
}
Curl_mutex_acquire(tsd->mtx);
tsd->done = 1;
Curl_mutex_release(tsd->mtx);
if(tsd->done) {
/* too late, gotta clean up the mess */
Curl_mutex_release(tsd->mtx);
destroy_thread_sync_data(tsd);
free(td);
}
else {
tsd->done = 1;
Curl_mutex_release(tsd->mtx);
}
return 0;
}
@ -294,6 +307,7 @@ static unsigned int CURL_STDCALL getaddrinfo_thread (void *arg)
static unsigned int CURL_STDCALL gethostbyname_thread (void *arg)
{
struct thread_sync_data *tsd = (struct thread_sync_data *)arg;
struct thread_data *td = tsd->td;
tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port);
@ -304,8 +318,16 @@ static unsigned int CURL_STDCALL gethostbyname_thread (void *arg)
}
Curl_mutex_acquire(tsd->mtx);
tsd->done = 1;
Curl_mutex_release(tsd->mtx);
if(tsd->done) {
/* too late, gotta clean up the mess */
Curl_mutex_release(tsd->mtx);
destroy_thread_sync_data(tsd);
free(td);
}
else {
tsd->done = 1;
Curl_mutex_release(tsd->mtx);
}
return 0;
}
@ -317,21 +339,37 @@ static unsigned int CURL_STDCALL gethostbyname_thread (void *arg)
*/
static void destroy_async_data (struct Curl_async *async)
{
if(async->os_specific) {
struct thread_data *td = (struct thread_data*) async->os_specific;
int done;
/*
* if the thread is still blocking in the resolve syscall, detach it and
* let the thread do the cleanup...
*/
Curl_mutex_acquire(td->tsd.mtx);
done = td->tsd.done;
td->tsd.done = 1;
Curl_mutex_release(td->tsd.mtx);
if(!done) {
Curl_thread_destroy(td->thread_hnd);
}
else {
if(td->thread_hnd != curl_thread_t_null)
Curl_thread_join(&td->thread_hnd);
destroy_thread_sync_data(&td->tsd);
free(async->os_specific);
}
}
async->os_specific = NULL;
if(async->hostname)
free(async->hostname);
if(async->os_specific) {
struct thread_data *td = (struct thread_data*) async->os_specific;
if(td->thread_hnd != curl_thread_t_null)
Curl_thread_join(&td->thread_hnd);
destroy_thread_sync_data(&td->tsd);
free(async->os_specific);
}
async->hostname = NULL;
async->os_specific = NULL;
}
/*
@ -357,7 +395,7 @@ static bool init_resolve_thread (struct connectdata *conn,
conn->async.dns = NULL;
td->thread_hnd = curl_thread_t_null;
if(!init_thread_sync_data(&td->tsd, hostname, port, hints))
if(!init_thread_sync_data(td, hostname, port, hints))
goto err_exit;
Curl_safefree(conn->async.hostname);

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -318,6 +318,48 @@ remove_entry_if_stale(struct SessionHandle *data, struct Curl_dns_entry *dns)
sigjmp_buf curl_jmpenv;
#endif
/*
* Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache.
*
* Curl_resolv() checks initially and multi_runsingle() checks each time
* it discovers the handle in the state WAITRESOLVE whether the hostname
* has already been resolved and the address has already been stored in
* the DNS cache. This short circuits waiting for a lot of pending
* lookups for the same hostname requested by different handles.
*
* Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
*/
struct Curl_dns_entry *
Curl_fetch_addr(struct connectdata *conn,
const char *hostname,
int port, int *stale)
{
char *entry_id = NULL;
struct Curl_dns_entry *dns = NULL;
size_t entry_len;
struct SessionHandle *data = conn->data;
/* Create an entry id, based upon the hostname and port */
entry_id = create_hostcache_id(hostname, port);
/* If we can't create the entry id, fail */
if(!entry_id)
return dns;
entry_len = strlen(entry_id);
/* See if its already in our dns cache */
dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1);
/* free the allocated entry_id again */
free(entry_id);
/* See whether the returned entry is stale. Done before we release lock */
*stale = remove_entry_if_stale(data, dns);
if(*stale)
dns = NULL; /* the memory deallocation is being handled by the hash */
return dns;
}
/*
* Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache.
@ -403,39 +445,22 @@ int Curl_resolv(struct connectdata *conn,
int port,
struct Curl_dns_entry **entry)
{
char *entry_id = NULL;
struct Curl_dns_entry *dns = NULL;
size_t entry_len;
struct SessionHandle *data = conn->data;
CURLcode result;
int rc = CURLRESOLV_ERROR; /* default to failure */
int stale, rc = CURLRESOLV_ERROR; /* default to failure */
*entry = NULL;
/* Create an entry id, based upon the hostname and port */
entry_id = create_hostcache_id(hostname, port);
/* If we can't create the entry id, fail */
if(!entry_id)
return rc;
entry_len = strlen(entry_id);
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
/* See if its already in our dns cache */
dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1);
dns = Curl_fetch_addr(conn, hostname, port, &stale);
/* free the allocated entry_id again */
free(entry_id);
infof(data, "Hostname was %sfound in DNS cache\n", dns?"":"NOT ");
/* See whether the returned entry is stale. Done before we release lock */
if(remove_entry_if_stale(data, dns)) {
infof(data, "Hostname was %sfound in DNS cache\n", dns||stale?"":"NOT ");
if(stale)
infof(data, "Hostname in DNS cache was stale, zapped\n");
dns = NULL; /* the memory deallocation is being handled by the hash */
}
if(dns) {
dns->inuse++; /* we use it! */

View File

@ -171,6 +171,17 @@ CURLcode Curl_addrinfo_callback(struct connectdata *conn,
const char *Curl_printable_address(const Curl_addrinfo *ip,
char *buf, size_t bufsize);
/*
* Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache.
*
* Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
*/
struct Curl_dns_entry *
Curl_fetch_addr(struct connectdata *conn,
const char *hostname,
int port,
int *stale);
/*
* Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache.
*

View File

@ -30,6 +30,7 @@
#include "connect.h"
#include "progress.h"
#include "easyif.h"
#include "share.h"
#include "multiif.h"
#include "sendf.h"
#include "timeval.h"
@ -1084,9 +1085,32 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* awaiting an asynch name resolve to complete */
{
struct Curl_dns_entry *dns = NULL;
struct connectdata *conn = data->easy_conn;
int stale;
/* check if we have the name resolved by now */
data->result = Curl_resolver_is_resolved(data->easy_conn, &dns);
if(data->share)
Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
dns = Curl_fetch_addr(conn, conn->host.name, (int)conn->port, &stale);
if(dns) {
dns->inuse++; /* we use it! */
#ifdef CURLRES_ASYNCH
conn->async.dns = dns;
conn->async.done = TRUE;
#endif
data->result = CURLRESOLV_RESOLVED;
infof(data, "Hostname was found in DNS cache\n");
}
if(stale)
infof(data, "Hostname in DNS cache was stale, zapped\n");
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
if(!dns)
data->result = Curl_resolver_is_resolved(data->easy_conn, &dns);
/* Update sockets here, because the socket(s) may have been
closed and the application thus needs to be told, even if it