]> granicus.if.org Git - libevent/commitdiff
test/http: request cancellation with resolving/{conn,write}-timeouts in progress
authorAzat Khuzhin <a3at.mail@gmail.com>
Sat, 12 Mar 2016 15:50:41 +0000 (18:50 +0300)
committerAzat Khuzhin <a3at.mail@gmail.com>
Wed, 23 Mar 2016 09:46:10 +0000 (12:46 +0300)
This patch adds 8 new tests:
- http/cancel
- http/cancel_by_host
- http/cancel_by_host_no_ns
- http/cancel_by_host_inactive_server
- http/cancel_inactive_server
- http/cancel_by_host_no_ns_inactive_server
- http/cancel_by_host_server_timeout
- http/cancel_server_timeout
- http/cancel_by_host_no_ns_server_timeout

This patches not 100% for http layer, but more for be_sock, but it was simpler
to add them here, plus it also shows some bugs with fd leaking in http layer.

Right now we have next picture (we can also play with timeouts/attempts for
evdns to make tests fail, IOW to track the failures even without valgrind):
$ valgrind --leak-check=full --show-reachable=yes --track-fds=yes --error-exitcode=1 regress --no-fork http/cancel..
http/cancel: OK
http/cancel_by_host: OK
http/cancel_by_host_no_ns: [msg] Nameserver 127.0.0.1:42489 has failed: request timed out.
[msg] All nameservers have failed
OK
http/cancel_by_host_inactive_server: OK
http/cancel_inactive_server: OK
http/cancel_by_host_no_ns_inactive_server: [msg] Nameserver 127.0.0.1:51370 has failed: request timed out.
[msg] All nameservers have failed
OK
http/cancel_by_host_server_timeout: OK
http/cancel_server_timeout: OK
http/cancel_by_host_no_ns_server_timeout: [msg] Nameserver 127.0.0.1:45054 has failed: request timed out.
[msg] All nameservers have failed
OK
9 tests ok.  (0 skipped)
==3202==
==3202== FILE DESCRIPTORS: 2309 open at exit.
...
==8403== HEAP SUMMARY:
==8403==     in use at exit: 1,104 bytes in 5 blocks
==8403==   total heap usage: 10,916 allocs, 10,911 frees, 1,458,818 bytes allocated
==8403==
==8403== 40 bytes in 1 blocks are indirectly lost in loss record 1 of 5
==8403==    at 0x4C2BBD5: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==8403==    by 0x4AAD2D: event_mm_calloc_ (event.c:3459)
==8403==    by 0x498E48: evbuffer_add_cb (buffer.c:3309)
==8403==    by 0x4A0DE2: bufferevent_socket_new (bufferevent_sock.c:366)
==8403==    by 0x4BF8BA: evhttp_connection_base_bufferevent_new (http.c:2369)
==8403==    by 0x4BFA6A: evhttp_connection_base_new (http.c:2421)
==8403==    by 0x460CFC: http_cancel_test (regress_http.c:1413)
==8403==    by 0x490965: testcase_run_bare_ (tinytest.c:105)
==8403==    by 0x490C47: testcase_run_one (tinytest.c:252)
==8403==    by 0x491586: tinytest_main (tinytest.c:434)
==8403==    by 0x47DFCD: main (regress_main.c:461)
==8403==
==8403== 136 bytes in 1 blocks are indirectly lost in loss record 2 of 5
==8403==    at 0x4C2BBD5: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==8403==    by 0x4AAD2D: event_mm_calloc_ (event.c:3459)
==8403==    by 0x491EDD: evbuffer_new (buffer.c:365)
==8403==    by 0x49A0AB: bufferevent_init_common_ (bufferevent.c:300)
==8403==    by 0x4A0D31: bufferevent_socket_new (bufferevent_sock.c:353)
==8403==    by 0x4BF8BA: evhttp_connection_base_bufferevent_new (http.c:2369)
==8403==    by 0x4BFA6A: evhttp_connection_base_new (http.c:2421)
==8403==    by 0x460CFC: http_cancel_test (regress_http.c:1413)
==8403==    by 0x490965: testcase_run_bare_ (tinytest.c:105)
==8403==    by 0x490C47: testcase_run_one (tinytest.c:252)
==8403==    by 0x491586: tinytest_main (tinytest.c:434)
==8403==    by 0x47DFCD: main (regress_main.c:461)
==8403==
==8403== 136 bytes in 1 blocks are indirectly lost in loss record 3 of 5
==8403==    at 0x4C2BBD5: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==8403==    by 0x4AAD2D: event_mm_calloc_ (event.c:3459)
==8403==    by 0x491EDD: evbuffer_new (buffer.c:365)
==8403==    by 0x49A0E8: bufferevent_init_common_ (bufferevent.c:305)
==8403==    by 0x4A0D31: bufferevent_socket_new (bufferevent_sock.c:353)
==8403==    by 0x4BF8BA: evhttp_connection_base_bufferevent_new (http.c:2369)
==8403==    by 0x4BFA6A: evhttp_connection_base_new (http.c:2421)
==8403==    by 0x460CFC: http_cancel_test (regress_http.c:1413)
==8403==    by 0x490965: testcase_run_bare_ (tinytest.c:105)
==8403==    by 0x490C47: testcase_run_one (tinytest.c:252)
==8403==    by 0x491586: tinytest_main (tinytest.c:434)
==8403==    by 0x47DFCD: main (regress_main.c:461)
==8403==
==8403== 528 bytes in 1 blocks are indirectly lost in loss record 4 of 5
==8403==    at 0x4C2BBD5: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==8403==    by 0x4AAD2D: event_mm_calloc_ (event.c:3459)
==8403==    by 0x4A0D02: bufferevent_socket_new (bufferevent_sock.c:350)
==8403==    by 0x4BF8BA: evhttp_connection_base_bufferevent_new (http.c:2369)
==8403==    by 0x4BFA6A: evhttp_connection_base_new (http.c:2421)
==8403==    by 0x460CFC: http_cancel_test (regress_http.c:1413)
==8403==    by 0x490965: testcase_run_bare_ (tinytest.c:105)
==8403==    by 0x490C47: testcase_run_one (tinytest.c:252)
==8403==    by 0x491586: tinytest_main (tinytest.c:434)
==8403==    by 0x47DFCD: main (regress_main.c:461)
==8403==
==8403== 1,104 (264 direct, 840 indirect) bytes in 1 blocks are definitely lost in loss record 5 of 5
==8403==    at 0x4C2BBD5: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==8403==    by 0x4AAD2D: event_mm_calloc_ (event.c:3459)
==8403==    by 0x4D0326: evdns_getaddrinfo (evdns.c:4682)
==8403==    by 0x4B1213: evutil_getaddrinfo_async_ (evutil.c:1568)
==8403==    by 0x4A1255: bufferevent_socket_connect_hostname (bufferevent_sock.c:517)
==8403==    by 0x4C00B6: evhttp_connection_connect_ (http.c:2582)
==8403==    by 0x4C02B8: evhttp_make_request (http.c:2637)
==8403==    by 0x4614EC: http_cancel_test (regress_http.c:1496)
==8403==    by 0x490965: testcase_run_bare_ (tinytest.c:105)
==8403==    by 0x490C47: testcase_run_one (tinytest.c:252)
==8403==    by 0x491586: tinytest_main (tinytest.c:434)
==8403==    by 0x47DFCD: main (regress_main.c:461)
==8403==
==8403== LEAK SUMMARY:
==8403==    definitely lost: 264 bytes in 1 blocks
==8403==    indirectly lost: 840 bytes in 4 blocks
==8403==      possibly lost: 0 bytes in 0 blocks
==8403==    still reachable: 0 bytes in 0 blocks
==8403==         suppressed: 0 bytes in 0 blocks

test/regress_http.c

index 22e71add4609625fbe0ccf3daacee0aa65349b8f..9cf9ee5a1b3f9838eb439b0b3fe5eeb115d9c678 100644 (file)
@@ -1268,6 +1268,21 @@ http_request_never_call(struct evhttp_request *req, void *arg)
        fprintf(stdout, "FAILED\n");
        exit(1);
 }
+static void
+http_failed_request_done(struct evhttp_request *req, void *arg)
+{
+       tt_assert(!req);
+end:
+       event_base_loopexit(arg, NULL);
+}
+static void
+http_timed_out_request_done(struct evhttp_request *req, void *arg)
+{
+       tt_assert(req);
+       tt_int_op(evhttp_request_get_response_code(req), !=, HTTP_OK);
+end:
+       event_base_loopexit(arg, NULL);
+}
 
 static void
 http_request_error_cb_with_cancel(enum evhttp_request_error error, void *arg)
@@ -1293,7 +1308,58 @@ http_do_cancel(evutil_socket_t fd, short what, void *arg)
        evhttp_cancel_request(req);
        ++test_ok;
 }
+static void
+http_no_write(struct evbuffer *buffer, const struct evbuffer_cb_info *info, void *arg)
+{
+       fprintf(stdout, "FAILED\n");
+       exit(1);
+}
+static void
+http_free_evcons(struct evhttp_connection **evcons)
+{
+       if (!evcons)
+               return;
+
+       struct evhttp_connection *evcon, **orig = evcons;
+       while ((evcon = *evcons++)) {
+               evhttp_connection_free(evcon);
+       }
+       free(orig);
+}
+/** fill the backlog to force server drop packages for timeouts */
+static struct evhttp_connection **
+http_fill_backlog(struct event_base *base, int port)
+{
+#define BACKLOG_SIZE 256
+               struct evhttp_connection **evcon = malloc(sizeof(*evcon) * (BACKLOG_SIZE + 1));
+               int i;
+
+               for (i = 0; i < BACKLOG_SIZE; ++i) {
+                       struct evhttp_request *req;
+
+                       evcon[i] = evhttp_connection_base_new(base, NULL, "127.0.0.1", port);
+                       tt_assert(evcon[i]);
+                       evhttp_connection_set_timeout(evcon[i], 5);
 
+                       req = evhttp_request_new(http_request_never_call, NULL);
+                       tt_assert(req);
+                       tt_int_op(evhttp_make_request(evcon[i], req, EVHTTP_REQ_GET, "/delay"), !=, -1);
+               }
+               evcon[i] = NULL;
+
+               return evcon;
+ end:
+               fprintf(stderr, "Couldn't fill the backlog");
+               return NULL;
+}
+
+enum http_cancel_test_type {
+       BASIC = 1,
+       BY_HOST = 2,
+       NO_NS = 4,
+       INACTIVE_SERVER = 8,
+       SERVER_TIMEOUT = 16,
+};
 static void
 http_cancel_test(void *arg)
 {
@@ -1301,7 +1367,33 @@ http_cancel_test(void *arg)
        ev_uint16_t port = 0;
        struct evhttp_connection *evcon = NULL;
        struct evhttp_request *req = NULL;
+       struct bufferevent *bufev = NULL;
        struct timeval tv;
+       struct evdns_base *dns_base = NULL;
+       ev_uint16_t portnum = 0;
+       char address[64];
+       struct evhttp *inactive_http = NULL;
+       struct event_base *inactive_base = NULL;
+       struct evhttp_connection **evcons = NULL;
+
+       enum http_cancel_test_type type;
+       type = (enum http_cancel_test_type)data->setup_data;
+
+       if (type & BY_HOST) {
+               tt_assert(regress_dnsserver(data->base, &portnum, search_table));
+
+               dns_base = evdns_base_new(data->base, 0/* init name servers */);
+               tt_assert(dns_base);
+
+               /** XXX: Hack the port to make timeout after resolving */
+               if (type & NO_NS)
+                       ++portnum;
+
+               evutil_snprintf(address, sizeof(address), "127.0.0.1:%d", portnum);
+               evdns_base_nameserver_ip_add(dns_base, address);
+               evdns_base_set_option(dns_base, "timeout:", "3");
+               evdns_base_set_option(dns_base, "attempts:", "1");
+       }
 
        exit_base = data->base;
 
@@ -1309,9 +1401,29 @@ http_cancel_test(void *arg)
 
        http = http_setup(&port, data->base, 0);
 
-       evcon = evhttp_connection_base_new(data->base, NULL, "127.0.0.1", port);
+       if (type & INACTIVE_SERVER) {
+               port = 0;
+               inactive_base = event_base_new();
+               inactive_http = http_setup(&port, inactive_base, 0);
+       }
+
+       if (type & SERVER_TIMEOUT)
+               evcons = http_fill_backlog(inactive_base ?: data->base, port);
+
+       evcon = evhttp_connection_base_new(
+               data->base, dns_base,
+               type & BY_HOST ? "localhost" : "127.0.0.1",
+               port);
+       if (type & INACTIVE_SERVER)
+               evhttp_connection_set_timeout(evcon, 5);
        tt_assert(evcon);
 
+       bufev = evhttp_connection_get_bufferevent(evcon);
+       /* Guarantee that we stack in connect() not after waiting EV_READ after
+        * write() */
+       if (type & SERVER_TIMEOUT)
+               evbuffer_add_cb(bufferevent_get_output(bufev), http_no_write, NULL);
+
        /*
         * At this point, we want to schedule a request to the HTTP
         * server using our make request method.
@@ -1335,12 +1447,24 @@ http_cancel_test(void *arg)
 
        event_base_dispatch(data->base);
 
-       tt_int_op(test_ok, ==, 3);
+       if (type & NO_NS || type & INACTIVE_SERVER)
+               tt_int_op(test_ok, ==, 2); /** no servers responses */
+       else
+               tt_int_op(test_ok, ==, 3);
 
        /* try to make another request over the same connection */
        test_ok = 0;
 
-       req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
+       http_free_evcons(evcons);
+       if (type & SERVER_TIMEOUT)
+               evcons = http_fill_backlog(inactive_base ?: data->base, port);
+
+       if (!(type & NO_NS) && (type & SERVER_TIMEOUT))
+               req = evhttp_request_new(http_timed_out_request_done, data->base);
+       else if ((type & INACTIVE_SERVER) || (type & NO_NS))
+               req = evhttp_request_new(http_failed_request_done, data->base);
+       else
+               req = evhttp_request_new(http_request_done, (void*) BASIC_REQUEST_BODY);
 
        /* Add the information that we care about */
        evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost");
@@ -1354,7 +1478,16 @@ http_cancel_test(void *arg)
        /* make another request: request empty reply */
        test_ok = 0;
 
-       req = evhttp_request_new(http_request_empty_done, data->base);
+       http_free_evcons(evcons);
+       if (type & SERVER_TIMEOUT)
+               evcons = http_fill_backlog(inactive_base ?: data->base, port);
+
+       if (!(type & NO_NS) && (type & SERVER_TIMEOUT))
+               req = evhttp_request_new(http_timed_out_request_done, data->base);
+       else if ((type & INACTIVE_SERVER) || (type & NO_NS))
+               req = evhttp_request_new(http_failed_request_done, data->base);
+       else
+               req = 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), "Empty", "itis");
@@ -1366,10 +1499,20 @@ http_cancel_test(void *arg)
        event_base_dispatch(data->base);
 
  end:
+       http_free_evcons(evcons);
+       if (bufev)
+               evbuffer_remove_cb(bufferevent_get_output(bufev), http_no_write, NULL);
        if (evcon)
                evhttp_connection_free(evcon);
        if (http)
                evhttp_free(http);
+       if (dns_base)
+               evdns_base_free(dns_base, 0);
+       regress_clean_dnsserver();
+       if (inactive_http)
+               evhttp_free(inactive_http);
+       if (inactive_base)
+               event_base_free(inactive_base);
 }
 
 static void
@@ -3770,13 +3913,6 @@ http_large_entity_test_done(struct evhttp_request *req, void *arg)
 end:
        event_base_loopexit(arg, NULL);
 }
-static void
-http_failed_request_done(struct evhttp_request *req, void *arg)
-{
-       tt_assert(!req);
-end:
-       event_base_loopexit(arg, NULL);
-}
 #ifndef WIN32
 static void
 http_expectation_failed_done(struct evhttp_request *req, void *arg)
@@ -3910,13 +4046,6 @@ static void http_data_length_constraints_test(void *arg)
 static void http_read_on_write_error_test(void *arg)
 { http_data_length_constraints_test_impl(arg, 1); }
 
-static void
-http_large_entity_non_lingering_test_done(struct evhttp_request *req, void *arg)
-{
-       tt_assert(!req);
-end:
-       event_base_loopexit(arg, NULL);
-}
 static void
 http_lingering_close_test_impl(void *arg, int lingering)
 {
@@ -3951,7 +4080,7 @@ http_lingering_close_test_impl(void *arg, int lingering)
        if (lingering)
                cb = http_large_entity_test_done;
        else
-               cb = http_large_entity_non_lingering_test_done;
+               cb = http_failed_request_done;
        req = evhttp_request_new(cb, data->base);
        tt_assert(req);
        evhttp_add_header(evhttp_request_get_output_headers(req), "Host", "somehost");
@@ -4342,8 +4471,10 @@ http_request_own_test(void *arg)
        { #name, run_legacy_test_fn, TT_ISOLATED|TT_LEGACY, &legacy_setup, \
                    http_##name##_test }
 
-#define HTTP(name) \
-       { #name, http_##name##_test, TT_ISOLATED, &basic_setup, NULL }
+#define HTTP_CAST_ARG(a) ((void *)(a))
+#define HTTP_N(title, name, arg) \
+       { #title, http_##name##_test, TT_ISOLATED, &basic_setup, HTTP_CAST_ARG(arg) }
+#define HTTP(name) HTTP_N(name, name, NULL)
 #define HTTPS(name) \
        { "https_" #name, https_##name##_test, TT_ISOLATED, &basic_setup, NULL }
 
@@ -4382,7 +4513,17 @@ struct testcase_t http_testcases[] = {
        { "uriencode", http_uriencode_test, 0, NULL, NULL },
        HTTP(basic),
        HTTP(simple),
-       HTTP(cancel),
+
+       HTTP_N(cancel, cancel, BASIC),
+       HTTP_N(cancel_by_host, cancel, BY_HOST),
+       HTTP_N(cancel_by_host_no_ns, cancel, BY_HOST | NO_NS),
+       HTTP_N(cancel_by_host_inactive_server, cancel, BY_HOST | INACTIVE_SERVER),
+       HTTP_N(cancel_inactive_server, cancel, INACTIVE_SERVER),
+       HTTP_N(cancel_by_host_no_ns_inactive_server, cancel, BY_HOST | NO_NS | INACTIVE_SERVER),
+       HTTP_N(cancel_by_host_server_timeout, cancel, BY_HOST | INACTIVE_SERVER | SERVER_TIMEOUT),
+       HTTP_N(cancel_server_timeout, cancel, INACTIVE_SERVER | SERVER_TIMEOUT),
+       HTTP_N(cancel_by_host_no_ns_server_timeout, cancel, BY_HOST | NO_NS | INACTIVE_SERVER | SERVER_TIMEOUT),
+
        HTTP(virtual_host),
        HTTP(post),
        HTTP(put),