Merge branch 'reuse-conn_address-on-retry-v11'

There is regression tests and also this code worked/tested during crawling a
huge number of pages (billions).

* reuse-conn_address-on-retry-v11:
  be_sock: bufferevent_socket_set_conn_address(): assert instead of silent no-op
  http: reuse connected address only with EVHTTP_CON_REUSE_CONNECTED_ADDR
  be_sock: sanity check in bufferevent_socket_set_conn_address()
  be: replace sockaddr_storage with sockaddr_in6 for conn_address
  be: we don't need to use getpeername() we we have conn_address
  be: replace conn_address by full struct instead of pointer
  test/http: cover retrying with saved conn_address by shutting down dns server
  http: use IP address that we got before (if any) during retrying
  bufferevent: move conn_address out from http into bufferevent
  be: make @sa const for bufferevent_socket_connect()
  util: make @sa const for evutil_socket_connect_()
This commit is contained in:
Azat Khuzhin 2015-08-18 21:19:11 +03:00
commit e045551558
9 changed files with 133 additions and 29 deletions

View File

@ -205,6 +205,18 @@ struct bufferevent_private {
/** Rate-limiting information for this bufferevent */ /** Rate-limiting information for this bufferevent */
struct bufferevent_rate_limit *rate_limiting; struct bufferevent_rate_limit *rate_limiting;
/* Saved conn_addr, to extract IP address from it.
*
* Because some servers may reset/close connection without waiting clients,
* in that case we can't extract IP address even in close_cb.
* So we need to save it, just after we connected to remote server, or
* after resolving (to avoid extra dns requests during retrying, since UDP
* is slow) */
union {
struct sockaddr_in6 in6;
struct sockaddr_in in;
} conn_address;
}; };
/** Possible operations for a control callback. */ /** Possible operations for a control callback. */
@ -392,6 +404,9 @@ int bufferevent_generic_adj_timeouts_(struct bufferevent *bev);
enum bufferevent_options bufferevent_get_options_(struct bufferevent *bev); enum bufferevent_options bufferevent_get_options_(struct bufferevent *bev);
const struct sockaddr*
bufferevent_socket_get_conn_address_(struct bufferevent *bev);
/** Internal use: We have just successfully read data into an inbuf, so /** Internal use: We have just successfully read data into an inbuf, so
* reset the read timeout (if any). */ * reset the read timeout (if any). */
#define BEV_RESET_GENERIC_READ_TIMEOUT(bev) \ #define BEV_RESET_GENERIC_READ_TIMEOUT(bev) \

View File

@ -100,6 +100,31 @@ const struct bufferevent_ops bufferevent_ops_socket = {
#define be_socket_add(ev, t) \ #define be_socket_add(ev, t) \
bufferevent_add_event_((ev), (t)) bufferevent_add_event_((ev), (t))
const struct sockaddr*
bufferevent_socket_get_conn_address_(struct bufferevent *bev)
{
struct bufferevent_private *bev_p =
EVUTIL_UPCAST(bev, struct bufferevent_private, bev);
return (struct sockaddr *)&bev_p->conn_address;
}
static void
bufferevent_socket_set_conn_address_fd(struct bufferevent_private *bev_p, int fd)
{
socklen_t len = sizeof(bev_p->conn_address);
struct sockaddr *addr = (struct sockaddr *)&bev_p->conn_address;
if (addr->sa_family != AF_UNSPEC)
getpeername(fd, addr, &len);
}
static void
bufferevent_socket_set_conn_address(struct bufferevent_private *bev_p,
struct sockaddr *addr, size_t addrlen)
{
EVUTIL_ASSERT(addrlen <= sizeof(bev_p->conn_address));
memcpy(&bev_p->conn_address, addr, addrlen);
}
static void static void
bufferevent_socket_outbuf_cb(struct evbuffer *buf, bufferevent_socket_outbuf_cb(struct evbuffer *buf,
const struct evbuffer_cb_info *cbinfo, const struct evbuffer_cb_info *cbinfo,
@ -239,6 +264,7 @@ bufferevent_writecb(evutil_socket_t fd, short event, void *arg)
goto done; goto done;
} else { } else {
connected = 1; connected = 1;
bufferevent_socket_set_conn_address_fd(bufev_p, fd);
#ifdef _WIN32 #ifdef _WIN32
if (BEV_IS_ASYNC(bufev)) { if (BEV_IS_ASYNC(bufev)) {
event_del(&bufev->ev_write); event_del(&bufev->ev_write);
@ -351,7 +377,7 @@ bufferevent_socket_new(struct event_base *base, evutil_socket_t fd,
int int
bufferevent_socket_connect(struct bufferevent *bev, bufferevent_socket_connect(struct bufferevent *bev,
struct sockaddr *sa, int socklen) const struct sockaddr *sa, int socklen)
{ {
struct bufferevent_private *bufev_p = struct bufferevent_private *bufev_p =
EVUTIL_UPCAST(bev, struct bufferevent_private, bev); EVUTIL_UPCAST(bev, struct bufferevent_private, bev);
@ -457,6 +483,7 @@ bufferevent_connect_getaddrinfo_cb(int result, struct evutil_addrinfo *ai,
/* XXX use the other addrinfos? */ /* XXX use the other addrinfos? */
/* XXX use this return value */ /* XXX use this return value */
bufferevent_socket_set_conn_address(bev_p, ai->ai_addr, (int)ai->ai_addrlen);
r = bufferevent_socket_connect(bev, ai->ai_addr, (int)ai->ai_addrlen); r = bufferevent_socket_connect(bev, ai->ai_addr, (int)ai->ai_addrlen);
(void)r; (void)r;
bufferevent_decref_and_unlock_(bev); bufferevent_decref_and_unlock_(bev);

View File

@ -523,7 +523,7 @@ evutil_socket_geterror(evutil_socket_t sock)
/* XXX we should use an enum here. */ /* XXX we should use an enum here. */
/* 2 for connection refused, 1 for connected, 0 for not yet, -1 for error. */ /* 2 for connection refused, 1 for connected, 0 for not yet, -1 for error. */
int int
evutil_socket_connect_(evutil_socket_t *fd_ptr, struct sockaddr *sa, int socklen) evutil_socket_connect_(evutil_socket_t *fd_ptr, const struct sockaddr *sa, int socklen)
{ {
int made_fd = 0; int made_fd = 0;

View File

@ -101,13 +101,6 @@ struct evhttp_connection {
struct event_base *base; struct event_base *base;
struct evdns_base *dns_base; struct evdns_base *dns_base;
int ai_family; int ai_family;
/* Saved conn_addr, to extract IP address from it.
*
* Because some servers may reset/close connection without waiting clients,
* in that case we can't extract IP address even in close_cb.
* So we need to save it, just after we connected to remote server. */
struct sockaddr_storage *conn_address;
}; };
/* A callback for an http server */ /* A callback for an http server */

49
http.c
View File

@ -1196,9 +1196,6 @@ evhttp_connection_free(struct evhttp_connection *evcon)
if (evcon->address != NULL) if (evcon->address != NULL)
mm_free(evcon->address); mm_free(evcon->address);
if (evcon->conn_address != NULL)
mm_free(evcon->conn_address);
mm_free(evcon); mm_free(evcon);
} }
@ -1452,7 +1449,6 @@ evhttp_connection_cb(struct bufferevent *bufev, short what, void *arg)
struct evhttp_connection *evcon = arg; struct evhttp_connection *evcon = arg;
int error; int error;
ev_socklen_t errsz = sizeof(error); ev_socklen_t errsz = sizeof(error);
socklen_t conn_address_len = sizeof(*evcon->conn_address);
if (evcon->fd == -1) if (evcon->fd == -1)
evcon->fd = bufferevent_getfd(bufev); evcon->fd = bufferevent_getfd(bufev);
@ -1503,14 +1499,6 @@ evhttp_connection_cb(struct bufferevent *bufev, short what, void *arg)
evcon->retry_cnt = 0; evcon->retry_cnt = 0;
evcon->state = EVCON_IDLE; evcon->state = EVCON_IDLE;
if (!evcon->conn_address) {
evcon->conn_address = mm_malloc(sizeof(*evcon->conn_address));
}
if (getpeername(evcon->fd, (struct sockaddr *)evcon->conn_address, &conn_address_len)) {
mm_free(evcon->conn_address);
evcon->conn_address = NULL;
}
/* reset the bufferevent cbs */ /* reset the bufferevent cbs */
bufferevent_setcb(evcon->bufev, bufferevent_setcb(evcon->bufev,
evhttp_read_cb, evhttp_read_cb,
@ -2346,6 +2334,20 @@ void evhttp_connection_set_family(struct evhttp_connection *evcon,
evcon->ai_family = family; evcon->ai_family = family;
} }
int evhttp_connection_set_flags(struct evhttp_connection *evcon,
int flags)
{
if (flags & ~(EVHTTP_CON_REUSE_CONNECTED_ADDR)) {
return 1;
}
evcon->flags &= ~(EVHTTP_CON_REUSE_CONNECTED_ADDR);
evcon->flags |= EVHTTP_CON_REUSE_CONNECTED_ADDR;
return 0;
}
void void
evhttp_connection_set_base(struct evhttp_connection *evcon, evhttp_connection_set_base(struct evhttp_connection *evcon,
struct event_base *base) struct event_base *base)
@ -2423,13 +2425,16 @@ evhttp_connection_get_peer(struct evhttp_connection *evcon,
const struct sockaddr* const struct sockaddr*
evhttp_connection_get_addr(struct evhttp_connection *evcon) evhttp_connection_get_addr(struct evhttp_connection *evcon)
{ {
return (struct sockaddr *)evcon->conn_address; return bufferevent_socket_get_conn_address_(evcon->bufev);
} }
int int
evhttp_connection_connect_(struct evhttp_connection *evcon) evhttp_connection_connect_(struct evhttp_connection *evcon)
{ {
int old_state = evcon->state; int old_state = evcon->state;
const char *address = evcon->address;
const struct sockaddr *sa = evhttp_connection_get_addr(evcon);
int ret;
if (evcon->state == EVCON_CONNECTING) if (evcon->state == EVCON_CONNECTING)
return (0); return (0);
@ -2470,8 +2475,22 @@ evhttp_connection_connect_(struct evhttp_connection *evcon)
evcon->state = EVCON_CONNECTING; evcon->state = EVCON_CONNECTING;
if (bufferevent_socket_connect_hostname(evcon->bufev, evcon->dns_base, if (evcon->flags & EVHTTP_CON_REUSE_CONNECTED_ADDR &&
evcon->ai_family, evcon->address, evcon->port) < 0) { sa &&
(sa->sa_family == AF_INET || sa->sa_family == AF_INET6)) {
int socklen;
if (sa->sa_family == AF_INET) {
socklen = sizeof(struct sockaddr_in);
} else if (sa->sa_family == AF_INET6) {
socklen = sizeof(struct sockaddr_in6);
}
ret = bufferevent_socket_connect(evcon->bufev, sa, socklen);
} else {
ret = bufferevent_socket_connect_hostname(evcon->bufev,
evcon->dns_base, evcon->ai_family, address, evcon->port);
}
if (ret < 0) {
evcon->state = old_state; evcon->state = old_state;
event_sock_warn(evcon->fd, "%s: connection to \"%s\" failed", event_sock_warn(evcon->fd, "%s: connection to \"%s\" failed",
__func__, evcon->address); __func__, evcon->address);

View File

@ -209,7 +209,7 @@ struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socke
@return 0 on success, -1 on failure. @return 0 on success, -1 on failure.
*/ */
EVENT2_EXPORT_SYMBOL EVENT2_EXPORT_SYMBOL
int bufferevent_socket_connect(struct bufferevent *, struct sockaddr *, int); int bufferevent_socket_connect(struct bufferevent *, const struct sockaddr *, int);
struct evdns_base; struct evdns_base;
/** /**

View File

@ -636,6 +636,18 @@ struct evhttp_connection *evhttp_connection_base_new(
void evhttp_connection_set_family(struct evhttp_connection *evcon, void evhttp_connection_set_family(struct evhttp_connection *evcon,
int family); int family);
#define EVHTTP_CON_REUSE_CONNECTED_ADDR 0x0008 /* reuse connection address on retry */
/**
* Set connection flags.
*
* @see EVHTTP_CON_*
* @return 0 on success, otherwise non zero (for example if flag doesn't
* supported).
*/
EVENT2_EXPORT_SYMBOL
int evhttp_connection_set_flags(struct evhttp_connection *evcon,
int flags);
/** Takes ownership of the request object /** Takes ownership of the request object
* *
* Can be used in a request callback to keep onto the request until * Can be used in a request callback to keep onto the request until
@ -728,7 +740,7 @@ void evhttp_connection_get_peer(struct evhttp_connection *evcon,
char **address, ev_uint16_t *port); char **address, ev_uint16_t *port);
/** Get the remote address associated with this connection. /** Get the remote address associated with this connection.
* extracted from getpeername(). * extracted from getpeername() OR from nameserver.
* *
* @return NULL if getpeername() return non success, * @return NULL if getpeername() return non success,
* or connection is not connected, * or connection is not connected,

View File

@ -3326,7 +3326,7 @@ http_make_web_server(evutil_socket_t fd, short what, void *arg)
} }
static void static void
http_connection_retry_test(void *arg) http_connection_retry_test_impl(void *arg, const char *addr, struct evdns_base *dns_base)
{ {
struct basic_test_data *data = arg; struct basic_test_data *data = arg;
ev_uint16_t port = 0; ev_uint16_t port = 0;
@ -3342,8 +3342,10 @@ http_connection_retry_test(void *arg)
evhttp_free(http); evhttp_free(http);
http = NULL; http = NULL;
evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); evcon = evhttp_connection_base_new(data->base, dns_base, addr, port);
tt_assert(evcon); tt_assert(evcon);
if (dns_base)
tt_assert(!evhttp_connection_set_flags(evcon, EVHTTP_CON_REUSE_CONNECTED_ADDR));
evhttp_connection_set_timeout(evcon, 1); evhttp_connection_set_timeout(evcon, 1);
/* also bind to local host */ /* also bind to local host */
@ -3377,6 +3379,9 @@ http_connection_retry_test(void *arg)
* now test the same but with retries * now test the same but with retries
*/ */
test_ok = 0; test_ok = 0;
/** Shutdown dns server, to test conn_address reusing */
if (dns_base)
regress_clean_dnsserver();
{ {
const struct timeval tv_timeout = { 0, 500000 }; const struct timeval tv_timeout = { 0, 500000 };
@ -3449,6 +3454,37 @@ http_connection_retry_test(void *arg)
evhttp_free(http); evhttp_free(http);
} }
static void
http_connection_retry_conn_address_test(void *arg)
{
struct basic_test_data *data = arg;
ev_uint16_t portnum = 0;
struct evdns_base *dns_base;
char address[64];
tt_assert(regress_dnsserver(data->base, &portnum, search_table));
dns_base = evdns_base_new(data->base, 0/* init name servers */);
tt_assert(dns_base);
/* Add ourself as the only nameserver, and make sure we really are
* the only nameserver. */
evutil_snprintf(address, sizeof(address), "127.0.0.1:%d", portnum);
evdns_base_nameserver_ip_add(dns_base, address);
http_connection_retry_test_impl(arg, "localhost", dns_base);
end:
if (dns_base)
evdns_base_free(dns_base, 0);
/** dnsserver will be cleaned in http_connection_retry_test_impl() */
}
static void
http_connection_retry_test(void *arg)
{
return http_connection_retry_test_impl(arg, "127.0.0.1", NULL);
}
static void static void
http_primitives(void *ptr) http_primitives(void *ptr)
{ {
@ -3976,6 +4012,8 @@ struct testcase_t http_testcases[] = {
HTTP(connection_fail), HTTP(connection_fail),
{ "connection_retry", http_connection_retry_test, TT_ISOLATED|TT_OFF_BY_DEFAULT, &basic_setup, NULL }, { "connection_retry", http_connection_retry_test, TT_ISOLATED|TT_OFF_BY_DEFAULT, &basic_setup, NULL },
{ "connection_retry_conn_address", http_connection_retry_conn_address_test,
TT_ISOLATED|TT_OFF_BY_DEFAULT, &basic_setup, NULL },
HTTP(data_length_constraints), HTTP(data_length_constraints),

View File

@ -261,7 +261,7 @@ int evutil_open_closeonexec_(const char *pathname, int flags, unsigned mode);
int evutil_read_file_(const char *filename, char **content_out, size_t *len_out, int evutil_read_file_(const char *filename, char **content_out, size_t *len_out,
int is_binary); int is_binary);
int evutil_socket_connect_(evutil_socket_t *fd_ptr, struct sockaddr *sa, int socklen); int evutil_socket_connect_(evutil_socket_t *fd_ptr, const struct sockaddr *sa, int socklen);
int evutil_socket_finished_connecting_(evutil_socket_t fd); int evutil_socket_finished_connecting_(evutil_socket_t fd);