On Tue, Feb 18, 2014 at 10:09:29AM +0100, Daniel Stenberg wrote:

> Okey, I checked this closer now and this is the full explanation to
> what happens. It seems to work as intended:

Thanks, your explanation makes perfect sense.

I think we should apply the patch below for git to consistently use the
multi interface. With this (and the recent patch for the NTLM issue), I
can do a whole smart-http clone over a single connection. This doesn't
make a huge difference for github.com, because the ssl session cache
eliminates most of the repeated work, but for servers which do not
implement ssl session caching, it may be more noticeable.

-- >8 --
Subject: 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

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 <p...@peff.net>
 http.c        | 24 +++++++++++++++---------
 http.h        |  9 +++++++++
 remote-curl.c |  5 +----
 3 files changed, 25 insertions(+), 13 deletions(-)

diff --git a/http.c b/http.c
index 70eaa26..1212c58 100644
--- a/http.c
+++ b/http.c
@@ -880,6 +880,20 @@ int handle_curl_result(struct slot_results *results)
+int run_one_slot(struct active_request_slot *slot,
+                struct slot_results *results)
+       slot->results = results;
+       if (!start_active_slot(slot)) {
+               snprintf(curl_errorstr, sizeof(curl_errorstr),
+                        "failed to start HTTP request");
+               return HTTP_START_FAILED;
+       }
+       run_active_slot(slot);
+       return handle_curl_result(results);
 static CURLcode curlinfo_strbuf(CURL *curl, CURLINFO info, struct strbuf *buf)
        char *ptr;
@@ -907,7 +921,6 @@ static int http_request(const char *url,
        int ret;
        slot = get_active_slot();
-       slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
        if (result == NULL) {
@@ -942,14 +955,7 @@ static int http_request(const char *url,
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(slot->curl, CURLOPT_ENCODING, "gzip");
-       if (start_active_slot(slot)) {
-               run_active_slot(slot);
-               ret = handle_curl_result(&results);
-       } else {
-               snprintf(curl_errorstr, sizeof(curl_errorstr),
-                        "failed to start HTTP request");
-               ret = HTTP_START_FAILED;
-       }
+       ret = run_one_slot(slot, &results);
        if (options && options->content_type)
                curlinfo_strbuf(slot->curl, CURLINFO_CONTENT_TYPE,
diff --git a/http.h b/http.h
index cd37d58..a828884 100644
--- a/http.h
+++ b/http.h
@@ -90,6 +90,15 @@ extern void finish_active_slot(struct active_request_slot 
 extern void finish_all_active_slots(void);
 extern int handle_curl_result(struct slot_results *results);
+ * This will run one slot to completion in a blocking manner, similar to how
+ * curl_easy_perform would work (but we don't want to use that, because
+ * we do not want to intermingle calls to curl_multi and curl_easy).
+ *
+ */
+int run_one_slot(struct active_request_slot *slot,
+                struct slot_results *results);
 extern void fill_active_slots(void);
 extern void add_fill_function(void *data, int (*fill)(void *));
diff --git a/remote-curl.c b/remote-curl.c
index 10cb011..52c2d96 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -423,11 +423,8 @@ static int run_slot(struct active_request_slot *slot,
        if (!results)
                results = &results_buf;
-       slot->results = results;
-       slot->curl_result = curl_easy_perform(slot->curl);
-       finish_active_slot(slot);
+       err = run_one_slot(slot, results);
-       err = handle_curl_result(results);
        if (err != HTTP_OK && err != HTTP_REAUTH) {
                error("RPC failed; result=%d, HTTP code = %ld",
                      results->curl_result, results->http_code);

To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to