mirror of
https://github.com/libevent/libevent.git
synced 2025-01-09 00:56:20 +08:00
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:
commit
e045551558
@ -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) \
|
||||||
|
@ -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);
|
||||||
|
2
evutil.c
2
evutil.c
@ -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;
|
||||||
|
|
||||||
|
@ -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
49
http.c
@ -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);
|
||||||
|
@ -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;
|
||||||
/**
|
/**
|
||||||
|
@ -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,
|
||||||
|
@ -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),
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user