From 2b9ec4c13c3483c22fdca8e9fccdc017b6135298 Mon Sep 17 00:00:00 2001 From: John Ohl Date: Sun, 26 Oct 2014 01:18:10 -0400 Subject: [PATCH 1/3] Implement interface that provides the ability to have an outbound evhttp_connection free itself once all requests have completed --- http-internal.h | 1 + http.c | 33 +++++++++++++++++++++++++++++++++ include/event2/http.h | 8 ++++++++ 3 files changed, 42 insertions(+) diff --git a/http-internal.h b/http-internal.h index 6f2f5b85..a83160c8 100644 --- a/http-internal.h +++ b/http-internal.h @@ -74,6 +74,7 @@ struct evhttp_connection { #define EVHTTP_CON_INCOMING 0x0001 /* only one request on it ever */ #define EVHTTP_CON_OUTGOING 0x0002 /* multiple requests possible */ #define EVHTTP_CON_CLOSEDETECT 0x0004 /* detecting if persistent close */ +#define EVHTTP_CON_AUTOFREE 0x0008 /* set when we want to auto free the connection */ struct timeval timeout; /* timeout for events */ int retry_cnt; /* retry count */ diff --git a/http.c b/http.c index 3941d179..e02297ac 100644 --- a/http.c +++ b/http.c @@ -769,6 +769,7 @@ evhttp_connection_done(struct evhttp_connection *evcon) { struct evhttp_request *req = TAILQ_FIRST(&evcon->requests); int con_outgoing = evcon->flags & EVHTTP_CON_OUTGOING; + int free_evcon = 0; if (con_outgoing) { /* idle or close the connection */ @@ -801,6 +802,12 @@ evhttp_connection_done(struct evhttp_connection *evcon) * need to detect if the other side closes it. */ evhttp_connection_start_detectclose(evcon); + } else if ((evcon->flags & EVHTTP_CON_AUTOFREE)) { + /* + * If we have no more requests that need completion + * and we're not waiting for the connection to close + */ + free_evcon = 1; } } else { /* @@ -819,6 +826,16 @@ evhttp_connection_done(struct evhttp_connection *evcon) if (con_outgoing && ((req->flags & EVHTTP_USER_OWNED) == 0)) { evhttp_request_free(req); } + + /* If this was the last request of an outgoing connection and we're + * not waiting to receive a connection close event and we want to + * automatically free the connection. We check to ensure our request + * list is empty one last time just in case our callback added a + * new request. + */ + if (free_evcon && TAILQ_FIRST(&evcon->requests) == NULL) { + evhttp_connection_free(evcon); + } } /* @@ -1174,6 +1191,11 @@ evhttp_connection_free(struct evhttp_connection *evcon) mm_free(evcon); } +void +evhttp_connection_free_on_completion(struct evhttp_connection *evcon) { + evcon->flags |= EVHTTP_CON_AUTOFREE; +} + void evhttp_connection_set_local_address(struct evhttp_connection *evcon, const char *address) @@ -1385,6 +1407,17 @@ evhttp_error_cb(struct bufferevent *bufev, short what, void *arg) */ EVUTIL_ASSERT(evcon->state == EVCON_IDLE); evhttp_connection_reset_(evcon); + + /* + * If we have no more requests that need completion + * and we want to auto-free the connection when all + * requests have been completed. + */ + if (TAILQ_FIRST(&evcon->requests) == NULL + && (evcon->flags & EVHTTP_CON_OUTGOING) + && (evcon->flags & EVHTTP_CON_AUTOFREE)) { + evhttp_connection_free(evcon); + } return; } diff --git a/include/event2/http.h b/include/event2/http.h index 7cade877..4284d5fc 100644 --- a/include/event2/http.h +++ b/include/event2/http.h @@ -675,6 +675,14 @@ void evhttp_connection_set_max_body_size(struct evhttp_connection* evcon, EVENT2_EXPORT_SYMBOL void evhttp_connection_free(struct evhttp_connection *evcon); +/** Disowns a given connection object + * + * Can be used to tell libevent to free the connection object after + * the last request has completed or failed. + */ +EVENT2_EXPORT_SYMBOL +void evhttp_connection_free_on_completion(struct evhttp_connection *evcon); + /** sets the ip address from which http connections are made */ EVENT2_EXPORT_SYMBOL void evhttp_connection_set_local_address(struct evhttp_connection *evcon, From b0e99244ce39ae0840a782ec66461837db7fc888 Mon Sep 17 00:00:00 2001 From: John Ohl Date: Wed, 29 Oct 2014 03:40:44 -0400 Subject: [PATCH 2/3] Add test for evhttp_connection_free_on_completion --- test/regress_http.c | 58 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/test/regress_http.c b/test/regress_http.c index d62dfbbf..35f6dd76 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -1159,6 +1159,63 @@ http_connection_async_test(void *arg) regress_clean_dnsserver(); } +static void +http_autofree_connection_test(void *arg) +{ + struct basic_test_data *data = arg; + ev_uint16_t port = 0; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req[2] = { NULL }; + + test_ok = 0; + http = http_setup(&port, data->base, 0); + + evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port); + tt_assert(evcon); + + /* + * At this point, we want to schedule two request to the HTTP + * server using our make request method. + */ + req[0] = evhttp_request_new(http_request_empty_done, data->base); + req[1] = evhttp_request_new(http_request_empty_done, data->base); + + /* Add the information that we care about */ + evhttp_add_header(evhttp_request_get_output_headers(req[0]), "Host", "somehost"); + evhttp_add_header(evhttp_request_get_output_headers(req[0]), "Connection", "close"); + evhttp_add_header(evhttp_request_get_output_headers(req[0]), "Empty", "itis"); + evhttp_add_header(evhttp_request_get_output_headers(req[1]), "Host", "somehost"); + evhttp_add_header(evhttp_request_get_output_headers(req[1]), "Connection", "close"); + evhttp_add_header(evhttp_request_get_output_headers(req[1]), "Empty", "itis"); + + /* We give ownership of the request to the connection */ + if (evhttp_make_request(evcon, req[0], EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("couldn't make request"); + } + if (evhttp_make_request(evcon, req[1], EVHTTP_REQ_GET, "/test") == -1) { + tt_abort_msg("couldn't make request"); + } + + /* + * Tell libevent to free the connection when the request completes + * We then set the evcon pointer to NULL since we don't want to free it + * when this function ends. + */ + evhttp_connection_free_on_completion(evcon); + evcon = NULL; + + event_base_dispatch(data->base); + + /* at this point, the http server should have no connection */ + tt_assert(TAILQ_FIRST(&http->connections) == NULL); + + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + static void http_request_never_call(struct evhttp_request *req, void *arg) { @@ -3897,6 +3954,7 @@ struct testcase_t http_testcases[] = { HTTP(failure), HTTP(connection), HTTP(persist_connection), + HTTP(autofree_connection), HTTP(connection_async), HTTP(close_detection), HTTP(close_detection_delay), From 10fe4ef300df9475ec37e817aa5777044f7d03b4 Mon Sep 17 00:00:00 2001 From: John Ohl Date: Sun, 16 Nov 2014 23:40:16 -0500 Subject: [PATCH 3/3] Prevent duplicate event_del on fd --- http.c | 1 + 1 file changed, 1 insertion(+) diff --git a/http.c b/http.c index e02297ac..449056c9 100644 --- a/http.c +++ b/http.c @@ -1265,6 +1265,7 @@ evhttp_connection_reset_(struct evhttp_connection *evcon) shutdown(evcon->fd, EVUTIL_SHUT_WR); evutil_closesocket(evcon->fd); + bufferevent_setfd(evcon->bufev, -1); evcon->fd = -1; }