Commit ec4f6e93 authored by Daniel Stenberg's avatar Daniel Stenberg
Browse files

- Emil Romanus found a problem and helped me repeat it. It occured when using

  the curl_multi_socket() API with HTTP pipelining enabled and could lead to
  the pipeline basically stalling for a very long period of time until it took
  off again.
parent b49dcfb5
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -8,6 +8,11 @@


Daniel Stenberg (28 May 2008)
- Emil Romanus found a problem and helped me repeat it. It occured when using
  the curl_multi_socket() API with HTTP pipelining enabled and could lead to
  the pipeline basically stalling for a very long period of time until it took
  off again.

- Jeff Weber reported memory leaks with aborted SCP and SFTP transfers and
  provided excellent repeat recipes. I fixed the cases I managed to reproduce
  but Jeff still got some (SCP) problems even after these fixes:
+2 −1
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ This release includes the following bugfixes:
 o builds fine for Haiku OS
 o follow redirect with only a new query string
 o SCP and SFTP memory leaks on aborted transfers
 o curl_multi_socket() and HTTP pipelining transfer stalls

This release includes the following known bugs:

@@ -61,6 +62,6 @@ advice from friends like these:
 Rafa Muyo, Andre Guibert de Bruet, Brock Noland, Sandor Feldi, Stefan Krause,
 David Shaw, Norbert Frese, Bart Whiteley, Jean-Francois Bertrand, Ben Van Hof,
 Yuriy Sosov, Christopher Palow, Yang Tse, Liam Healy, Nikolai Kondrashov,
 David Rosenstrauch, Andreas Faerber, Scott McCreary, Jeff Weber
 David Rosenstrauch, Andreas Faerber, Scott McCreary, Jeff Weber, Emil Romanus

        Thanks! (and sorry if I forgot to mention someone)
+58 −19
Original line number Diff line number Diff line
@@ -189,7 +189,7 @@ static int update_timer(struct Curl_multi *multi);
static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle,
                                              struct connectdata *conn);
static int checkPendPipeline(struct connectdata *conn);
static int moveHandleFromSendToRecvPipeline(struct SessionHandle *habdle,
static void moveHandleFromSendToRecvPipeline(struct SessionHandle *habdle,
                                             struct connectdata *conn);
static bool isHandleAtHead(struct SessionHandle *handle,
                           struct curl_llist *pipeline);
@@ -505,7 +505,7 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
     sockets that time-out or have actions will be dealt with. Since this
     handle has no action yet, we make sure it times out to get things to
     happen. */
  Curl_expire(easy->easy_handle, 10);
  Curl_expire(easy->easy_handle, 1);

  /* increase the node-counter */
  multi->num_easy++;
@@ -1286,13 +1286,19 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
        /* call this even if the readwrite function returned error */
        Curl_posttransfer(easy->easy_handle);

        /* When we follow redirects, must to go back to the CONNECT state */
        if(easy->easy_handle->req.newurl || retry) {
        /* we're no longer receving */
        Curl_removeHandleFromPipeline(easy->easy_handle,
                                      easy->easy_conn->recv_pipe);

        /* expire the new receiving pipeline head */
        if(easy->easy_conn->recv_pipe->head)
          Curl_expire(easy->easy_conn->recv_pipe->head->ptr, 1);

        /* Check if we can move pending requests to send pipe */
        checkPendPipeline(easy->easy_conn);

        /* When we follow redirects, must to go back to the CONNECT state */
        if(easy->easy_handle->req.newurl || retry) {
          if(!retry) {
            /* if the URL is a follow-location and not just a retried request
               then figure out the URL here */
@@ -1331,9 +1337,9 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
      checkPendPipeline(easy->easy_conn);

      if(easy->easy_conn->bits.stream_was_rewound) {
          /* This request read past its response boundary so we quickly
             let the other requests consume those bytes since there is no
             guarantee that the socket will become active again */
        /* This request read past its response boundary so we quickly let the
           other requests consume those bytes since there is no guarantee that
           the socket will become active again */
        result = CURLM_CALL_MULTI_PERFORM;
      }

@@ -1431,7 +1437,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
       get things to happen. Also, this makes it less important for callers of
       the curl_multi_* functions to bother about the CURLM_CALL_MULTI_PERFORM
       return code, as long as they deal with the timeouts properly. */
    Curl_expire(easy->easy_handle, 10);
    Curl_expire(easy->easy_handle, 1);

  return result;
}
@@ -1785,6 +1791,9 @@ static CURLMcode multi_socket(struct Curl_multi *multi,
       extracts a matching node if there is one */

    now = Curl_tvnow();
    now.tv_usec += 1000; /* to compensate for the truncating of 999us to 0ms,
                            we always add time here to make the comparison
                            below better */

    multi->timetree = Curl_splaygetbest(now, multi->timetree, &t);
    if(t) {
@@ -1962,6 +1971,7 @@ static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle,
static int checkPendPipeline(struct connectdata *conn)
{
  int result = 0;
  struct curl_llist_element *sendhead = conn->send_pipe->head;

  if (conn->server_supports_pipelining) {
    size_t pipeLen = conn->send_pipe->size + conn->recv_pipe->size;
@@ -1979,10 +1989,27 @@ static int checkPendPipeline(struct connectdata *conn)
      conn->now = Curl_tvnow();
  }

  if(result) {
    /* something moved, check for a new send pipeline leader */
    if(sendhead != conn->send_pipe->head) {
      /* this is a new one as head, expire it */
      conn->writechannel_inuse = FALSE; /* not in use yet */
      infof(conn->data, "%p is at send pipe head!\n",
            conn->send_pipe->head->ptr);
      Curl_expire(conn->send_pipe->head->ptr, 1);
    }
  }

  return result;
}

static int moveHandleFromSendToRecvPipeline(struct SessionHandle *handle,
/* Move this transfer from the sending list to the receiving list.

   Pay special attention to the new sending list "leader" as it needs to get
   checked to update what sockets it acts on.

 */
static void moveHandleFromSendToRecvPipeline(struct SessionHandle *handle,
                                            struct connectdata *conn)
{
  struct curl_llist_element *curr;
@@ -1992,12 +2019,24 @@ static int moveHandleFromSendToRecvPipeline(struct SessionHandle *handle,
    if(curr->ptr == handle) {
      Curl_llist_move(conn->send_pipe, curr,
                      conn->recv_pipe, conn->recv_pipe->tail);
      return 1; /* we moved a handle */

      if(conn->send_pipe->head) {
        /* Since there's a new easy handle at the start of the send pipeline,
           set its timeout value to 1ms to make it trigger instantly */
        conn->writechannel_inuse = FALSE; /* not used now */
        infof(conn->data, "%p is at send pipe head B!\n",
              conn->send_pipe->head->ptr);
        Curl_expire(conn->send_pipe->head->ptr, 1);
      }

      /* The receiver's list is not really interesting here since either this
         handle is now first in the list and we'll deal with it soon, or
         another handle is already first and thus is already taken care of */

      break; /* we're done! */
    }
    curr = curr->next;
  }

  return 0;
}

static bool isHandleAtHead(struct SessionHandle *handle,
@@ -2073,8 +2112,8 @@ void Curl_expire(struct SessionHandle *data, long milli)

    *nowp = set;
#if 0
    infof(data, "Expire at %ld / %ld (%ldms)\n",
          (long)nowp->tv_sec, (long)nowp->tv_usec, milli);
    infof(data, "Expire at %ld / %ld (%ldms) %p\n",
          (long)nowp->tv_sec, (long)nowp->tv_usec, milli, data);
#endif
    data->state.timenode.payload = data;
    multi->timetree = Curl_splayinsert(*nowp,