]> granicus.if.org Git - libevent/commitdiff
http: fix connection retries when there more then one request for connection
authorAzat Khuzhin <azat@libevent.org>
Fri, 7 Dec 2018 18:46:27 +0000 (21:46 +0300)
committerAzat Khuzhin <azat@libevent.org>
Wed, 12 Dec 2018 18:46:27 +0000 (21:46 +0300)
We should not attemp to establishe the connection if there is retry
timer active, since otherwise there will be a bug.

Imagine next situation:
  con = evhttp_connection_base_new()
  evhttp_connection_set_retries(con, 2)
  req = evhttp_request_new()
  evhttp_make_request(con, req, ...)
  # failed during connecting, and timer for 2 second scheduler (retry_ev)

Then another request scheduled for this evcon:
  evhttp_make_request(con, req, ...)
  # got request from server,
  # and now it tries to read the response from the server
  # (req.kind == EVHTTP_RESPONSE)
  #
  # but at this point retry_ev scheduled,
  # and it schedules the connect again,
  # and after the connect will succeeed, it will pick request with
  # EVHTTP_RESPONSE for sending and this is completelly wrong and will
  # fail in evhttp_make_header_response() since there is no
  # "http_server" for this evcon

This was a long standing issue, that I came across few years ago
firstly, bad only now I had time to dig into it (but right now it was
pretty simple, by limiting amount of CPU for the process and using rr
for debug to go back and forth).

http.c

diff --git a/http.c b/http.c
index b49bc04cf0bbf857d8d9a356492766f2cdafcede..3f2f40f46babca6f238bdecc0a9df9bacd0017f5 100644 (file)
--- a/http.c
+++ b/http.c
@@ -1290,6 +1290,8 @@ evhttp_request_dispatch(struct evhttp_connection* evcon)
        if (req == NULL)
                return;
 
+       EVUTIL_ASSERT(req->kind == EVHTTP_REQUEST);
+
        /* delete possible close detection events */
        evhttp_connection_stop_detectclose(evcon);
 
@@ -2644,6 +2646,10 @@ evhttp_make_request(struct evhttp_connection *evcon,
 
        TAILQ_INSERT_TAIL(&evcon->requests, req, next);
 
+       /* We do not want to conflict with retry_ev */
+       if (evcon->retry_cnt)
+               return (0);
+
        /* If the connection object is not connected; make it so */
        if (!evhttp_connected(evcon)) {
                int res = evhttp_connection_connect_(evcon);
@@ -2654,7 +2660,7 @@ evhttp_make_request(struct evhttp_connection *evcon,
                if (res != 0)
                        TAILQ_REMOVE(&evcon->requests, req, next);
 
-               return res;
+               return (res);
        }
 
        /*