http: never use curl_easy_perform

We currently don't reuse http connections when fetching via the smart-http protocol. This is bad because the TCP handshake introduces latency, and especially because SSL connection setup may be non-trivial. We can fix it by consistently using curl's "multi" interface. The reason is rather complicated: Our http code has two ways of being used: queuing many "slots" to be fetched in parallel, or fetching a single request in a blocking manner. The parallel code is built on curl's "multi" interface. Most of the single-request code uses http_request, which is built on top of the parallel code (we just feed it one slot, and wait until it finishes). However, one could also accomplish the single-request scheme by avoiding curl's multi interface entirely and just using curl_easy_perform. This is simpler, and is used by post_rpc in the smart-http protocol. It does work to use the same curl handle in both contexts, as long as it is not at the same time. However, internally curl may not share all of the cached resources between both contexts. In particular, a connection formed using the "multi" code will go into a reuse pool connected to the "multi" object. Further requests using the "easy" interface will not be able to reuse that connection. The smart http protocol does ref discovery via http_request, which uses the "multi" interface, and then follows up with the "easy" interface for its rpc calls. As a result, we make two HTTP connections rather than reusing a single one. We could teach the ref discovery to use the "easy" interface. But it is only once we have done this discovery that we know whether the protocol will be smart or dumb. If it is dumb, then our further requests, which want to fetch objects in parallel, will not be able to reuse the same connection. Instead, this patch switches post_rpc to build on the parallel interface, which means that we use it consistently everywhere. It's a little more complicated to use, but since we have the infrastructure already, it doesn't add any code; we can just factor out the relevant bits from http_request. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Feb 18, 2014 at 05:34 UTC beed336c3e35acfd7aad9033eb9294e42b9530af
3 files changed +25 -13
http.c
+15 -9
@@ -880,6 +880,20 @@ int handle_curl_result(struct slot_results *results)
880 }
881 }
882
883 +int run_one_slot(struct active_request_slot *slot,
884 + struct slot_results *results)
885 +{
886 + slot->results = results;
887 + if (!start_active_slot(slot)) {
888 + snprintf(curl_errorstr, sizeof(curl_errorstr),
889 + "failed to start HTTP request");
890 + return HTTP_START_FAILED;
891 + }
892 +
893 + run_active_slot(slot);
894 + return handle_curl_result(results);
895 +}
896 +
897 static CURLcode curlinfo_strbuf(CURL *curl, CURLINFO info, struct strbuf *buf)
898 {
899 char *ptr;
@@ -907,7 +921,6 @@ static int http_request(const char *url,
921 int ret;
922
923 slot = get_active_slot();
910 - slot->results = &results;
924 curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
925
926 if (result == NULL) {
@@ -942,14 +955,7 @@ static int http_request(const char *url,
955 curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
956 curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "gzip");
957
945 - if (start_active_slot(slot)) {
946 - run_active_slot(slot);
947 - ret = handle_curl_result(&results);
948 - } else {
949 - snprintf(curl_errorstr, sizeof(curl_errorstr),
950 - "failed to start HTTP request");
951 - ret = HTTP_START_FAILED;
952 - }
958 + ret = run_one_slot(slot, &results);
959
960 if (options && options->content_type)
961 curlinfo_strbuf(slot->curl, CURLINFO_CONTENT_TYPE,
http.h
+9
@@ -90,6 +90,15 @@ extern void finish_active_slot(struct active_request_slot *slot);
90 extern void finish_all_active_slots(void);
91 extern int handle_curl_result(struct slot_results *results);
92
93 +/*
94 + * This will run one slot to completion in a blocking manner, similar to how
95 + * curl_easy_perform would work (but we don't want to use that, because
96 + * we do not want to intermingle calls to curl_multi and curl_easy).
97 + *
98 + */
99 +int run_one_slot(struct active_request_slot *slot,
100 + struct slot_results *results);
101 +
102 #ifdef USE_CURL_MULTI
103 extern void fill_active_slots(void);
104 extern void add_fill_function(void *data, int (*fill)(void *));
remote-curl.c
+1 -4
@@ -423,11 +423,8 @@ static int run_slot(struct active_request_slot *slot,
423 if (!results)
424 results = &results_buf;
425
426 - slot->results = results;
427 - slot->curl_result = curl_easy_perform(slot->curl);
428 - finish_active_slot(slot);
426 + err = run_one_slot(slot, results);
427
430 - err = handle_curl_result(results);
428 if (err != HTTP_OK && err != HTTP_REAUTH) {
429 error("RPC failed; result=%d, HTTP code = %ld",
430 results->curl_result, results->http_code);