]> granicus.if.org Git - curl/commitdiff
http2: Don't call nghttp2_session_mem_recv while it is paused by a stream
authorTatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Thu, 7 May 2015 15:52:48 +0000 (17:52 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 18 May 2015 06:57:18 +0000 (08:57 +0200)
lib/http.h
lib/http2.c

index 13fa1d99b7ae0e134dfce3cfbbc67ff95020a503..0b4aac3434b2d77127a0fd9f3dd9f969b78bb071 100644 (file)
@@ -201,6 +201,8 @@ struct http_conn {
   const uint8_t *upload_mem; /* points to a buffer to read from */
   size_t upload_len; /* size of the buffer 'upload_mem' points to */
   size_t upload_left; /* number of bytes left to upload */
+  int32_t pause_stream_id; /* stream ID which paused
+                              nghttp2_session_mem_recv */
 
   /* this is a hash of all individual streams (SessionHandle structs) */
   struct curl_hash streamsh;
index 78a09af8eeca01c858e0adebbb0721df6ddccddd..e9eb81180fb0529bc32e498545632733adb1a48a 100644 (file)
@@ -372,6 +372,7 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
     stream->data = data + nread;
     stream->datalen = len - nread;
     DEBUGF(infof(data_s, "NGHTTP2_ERR_PAUSE - out of buffer\n"));
+    conn->proto.httpc.pause_stream_id = stream_id;
     return NGHTTP2_ERR_PAUSE;
   }
   return 0;
@@ -762,8 +763,12 @@ CURLcode Curl_http2_request_upgrade(Curl_send_buffer *req,
   return result;
 }
 
-static ssize_t http2_handle_stream_close(struct SessionHandle *data,
+static ssize_t http2_handle_stream_close(struct http_conn *httpc,
+                                         struct SessionHandle *data,
                                          struct HTTP *stream, CURLcode *err) {
+  if(httpc->pause_stream_id == stream->stream_id) {
+    httpc->pause_stream_id = 0;
+  }
   /* Reset to FALSE to prevent infinite loop in readwrite_data
    function. */
   stream->closed = FALSE;
@@ -798,7 +803,7 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
      otherwise, we may be going to read from underlying connection,
      and gets EAGAIN, and we will get stuck there. */
   if(stream->memlen == 0 && stream->closed) {
-    return http2_handle_stream_close(data, stream, err);
+    return http2_handle_stream_close(httpc, data, stream, err);
   }
 
   /* Nullify here because we call nghttp2_session_send() and they
@@ -835,6 +840,10 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
 
     infof(data, "%zu data bytes written\n", nread);
     if(stream->datalen == 0) {
+      DEBUGF(infof(data, "Unpaused by stream %x\n", stream->stream_id));
+      assert(httpc->pause_stream_id == stream->stream_id);
+      httpc->pause_stream_id = 0;
+
       stream->data = NULL;
       stream->datalen = 0;
     }
@@ -858,6 +867,18 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
       stream->mem = mem;
     }
   }
+  else if(httpc->pause_stream_id) {
+    /* If a stream paused nghttp2_session_mem_recv previously, and has
+       not processed all data, it still refers to the buffer in
+       nghttp2_session.  If we call nghttp2_session_mem_recv(), we may
+       overwrite that buffer.  To avoid that situation, just return
+       here with CURLE_AGAIN.  This could be busy loop since data in
+       socket is not read.  But it seems that usually streams are
+       notified with its drain property, and socket is read again
+       quickly. */
+    *err = CURLE_AGAIN;
+    return -1;
+  }
   else {
     char *inbuf;
     /* remember where to store incoming data for this stream and how big the
@@ -939,7 +960,7 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
   /* If stream is closed, return 0 to signal the http routine to close
      the connection */
   if(stream->closed) {
-    return http2_handle_stream_close(data, stream, err);
+    return http2_handle_stream_close(httpc, data, stream, err);
   }
   *err = CURLE_AGAIN;
   DEBUGF(infof(data, "http2_recv returns -1, AGAIN\n"));
@@ -1169,6 +1190,8 @@ CURLcode Curl_http2_setup(struct connectdata *conn)
   httpc->inbuflen = 0;
   httpc->nread_inbuf = 0;
 
+  httpc->pause_stream_id = 0;
+
   conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
   conn->httpversion = 20;
   conn->bundle->server_supports_pipelining = TRUE;