@@ -918,31 +918,51 @@ inline ssize_t Http2Session::OnCallbackPadding(size_t frameLen,
918918// various callback functions. Each of these will typically result in a call
919919// out to JavaScript so this particular function is rather hot and can be
920920// quite expensive. This is a potential performance optimization target later.
921- inline ssize_t Http2Session::Write (const uv_buf_t * bufs, size_t nbufs) {
922- size_t total = 0 ;
923- // Note that nghttp2_session_mem_recv is a synchronous operation that
924- // will trigger a number of other callbacks. Those will, in turn have
925- // multiple side effects.
926- for (size_t n = 0 ; n < nbufs; n++) {
927- DEBUG_HTTP2SESSION2 (this , " receiving %d bytes [wants data? %d]" ,
928- bufs[n].len ,
929- nghttp2_session_want_read (session_));
930- ssize_t ret =
931- nghttp2_session_mem_recv (session_,
932- reinterpret_cast <uint8_t *>(bufs[n].base ),
933- bufs[n].len );
934- CHECK_NE (ret, NGHTTP2_ERR_NOMEM);
935-
936- if (ret < 0 )
937- return ret;
921+ ssize_t Http2Session::ConsumeHTTP2Data () {
922+ CHECK_NE (stream_buf_.base , nullptr );
923+ CHECK_LT (stream_buf_offset_, stream_buf_.len );
924+ size_t read_len = stream_buf_.len - stream_buf_offset_;
925+
926+ DEBUG_HTTP2SESSION2 (this , " receiving %d bytes [wants data? %d]" ,
927+ read_len,
928+ nghttp2_session_want_read (session_));
929+ flags_ &= ~SESSION_STATE_NGHTTP2_RECV_PAUSED;
930+ ssize_t ret =
931+ nghttp2_session_mem_recv (session_,
932+ reinterpret_cast <uint8_t *>(stream_buf_.base ) +
933+ stream_buf_offset_,
934+ read_len);
935+ CHECK_NE (ret, NGHTTP2_ERR_NOMEM);
936+
937+ if (flags_ & SESSION_STATE_NGHTTP2_RECV_PAUSED) {
938+ CHECK_NE (flags_ & SESSION_STATE_READING_STOPPED, 0 );
939+
940+ CHECK_GT (ret, 0 );
941+ CHECK_LE (static_cast <size_t >(ret), read_len);
938942
939- total += ret;
943+ if (static_cast <size_t >(ret) < read_len) {
944+ // Mark the remainder of the data as available for later consumption.
945+ stream_buf_offset_ += ret;
946+ return ret;
947+ }
940948 }
949+
950+ // We are done processing the current input chunk.
951+ DecrementCurrentSessionMemory (stream_buf_.len );
952+ stream_buf_offset_ = 0 ;
953+ stream_buf_ab_.Reset ();
954+ free (stream_buf_allocation_.base );
955+ stream_buf_allocation_ = uv_buf_init (nullptr , 0 );
956+ stream_buf_ = uv_buf_init (nullptr , 0 );
957+
958+ if (ret < 0 )
959+ return ret;
960+
941961 // Send any data that was queued up while processing the received data.
942962 if (!IsDestroyed ()) {
943963 SendPendingData ();
944964 }
945- return total ;
965+ return ret ;
946966}
947967
948968
@@ -1238,6 +1258,7 @@ inline int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
12381258 size_t offset = data - reinterpret_cast <uint8_t *>(session->stream_buf_ .base );
12391259
12401260 // Verify that the data offset is inside the current read buffer.
1261+ CHECK_GE (offset, session->stream_buf_offset_ );
12411262 CHECK_LE (offset, session->stream_buf_ .len );
12421263 CHECK_LE (offset + len, session->stream_buf_ .len );
12431264
@@ -1250,6 +1271,16 @@ inline int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
12501271 else
12511272 nghttp2_session_consume_stream (handle, id, len);
12521273
1274+ // If we have a gathered a lot of data for output, try sending it now.
1275+ if (session->outgoing_length_ > 4096 ) session->SendPendingData ();
1276+
1277+ // If we are currently waiting for a write operation to finish, we should
1278+ // tell nghttp2 that we want to wait before we process more input data.
1279+ if (session->flags_ & SESSION_STATE_WRITE_IN_PROGRESS) {
1280+ session->flags_ |= SESSION_STATE_NGHTTP2_RECV_PAUSED;
1281+ return NGHTTP2_ERR_PAUSE;
1282+ }
1283+
12531284 return 0 ;
12541285}
12551286
@@ -1597,6 +1628,11 @@ void Http2Session::OnStreamAfterWriteImpl(WriteWrap* w, int status, void* ctx) {
15971628 session->stream_ ->ReadStart ();
15981629 }
15991630
1631+ // If there is more incoming data queued up, consume it.
1632+ if (session->stream_buf_offset_ > 0 ) {
1633+ session->ConsumeHTTP2Data ();
1634+ }
1635+
16001636 if (!(session->flags_ & SESSION_STATE_WRITE_SCHEDULED)) {
16011637 // Schedule a new write if nghttp2 wants to send data.
16021638 session->MaybeScheduleWrite ();
@@ -1654,6 +1690,7 @@ void Http2Session::ClearOutgoing(int status) {
16541690
16551691 if (outgoing_buffers_.size () > 0 ) {
16561692 outgoing_storage_.clear ();
1693+ outgoing_length_ = 0 ;
16571694
16581695 std::vector<nghttp2_stream_write> current_outgoing_buffers_;
16591696 current_outgoing_buffers_.swap (outgoing_buffers_);
@@ -1680,6 +1717,11 @@ void Http2Session::ClearOutgoing(int status) {
16801717 }
16811718}
16821719
1720+ void Http2Session::PushOutgoingBuffer (nghttp2_stream_write&& write) {
1721+ outgoing_length_ += write.buf .len ;
1722+ outgoing_buffers_.emplace_back (std::move (write));
1723+ }
1724+
16831725// Queue a given block of data for sending. This always creates a copy,
16841726// so it is used for the cases in which nghttp2 requests sending of a
16851727// small chunk of data.
@@ -1692,7 +1734,7 @@ void Http2Session::CopyDataIntoOutgoing(const uint8_t* src, size_t src_length) {
16921734 // of the outgoing_buffers_ vector may invalidate the pointer.
16931735 // The correct base pointers will be set later, before writing to the
16941736 // underlying socket.
1695- outgoing_buffers_. emplace_back (nghttp2_stream_write {
1737+ PushOutgoingBuffer (nghttp2_stream_write {
16961738 uv_buf_init (nullptr , src_length)
16971739 });
16981740}
@@ -1826,13 +1868,13 @@ int Http2Session::OnSendData(
18261868 if (write.buf .len <= length) {
18271869 // This write does not suffice by itself, so we can consume it completely.
18281870 length -= write.buf .len ;
1829- session->outgoing_buffers_ . emplace_back (std::move (write));
1871+ session->PushOutgoingBuffer (std::move (write));
18301872 stream->queue_ .pop ();
18311873 continue ;
18321874 }
18331875
18341876 // Slice off `length` bytes of the first write in the queue.
1835- session->outgoing_buffers_ . emplace_back (nghttp2_stream_write {
1877+ session->PushOutgoingBuffer (nghttp2_stream_write {
18361878 uv_buf_init (write.buf .base , length)
18371879 });
18381880 write.buf .base += length;
@@ -1842,7 +1884,7 @@ int Http2Session::OnSendData(
18421884
18431885 if (frame->data .padlen > 0 ) {
18441886 // Send padding if that was requested.
1845- session->outgoing_buffers_ . emplace_back (nghttp2_stream_write {
1887+ session->PushOutgoingBuffer (nghttp2_stream_write {
18461888 uv_buf_init (const_cast <char *>(zero_bytes_256), frame->data .padlen - 1 )
18471889 });
18481890 }
@@ -1896,8 +1938,6 @@ void Http2Session::OnStreamReadImpl(ssize_t nread,
18961938 Http2Scope h2scope (session);
18971939 CHECK_NE (session->stream_ , nullptr );
18981940 DEBUG_HTTP2SESSION2 (session, " receiving %d bytes" , nread);
1899- CHECK_EQ (session->stream_buf_allocation_ .base , nullptr );
1900- CHECK (session->stream_buf_ab_ .IsEmpty ());
19011941
19021942 // Only pass data on if nread > 0
19031943 if (nread <= 0 ) {
@@ -1913,26 +1953,34 @@ void Http2Session::OnStreamReadImpl(ssize_t nread,
19131953 return ;
19141954 }
19151955
1916- // Shrink to the actual amount of used data.
19171956 uv_buf_t buf = *buf_;
1918- buf.base = Realloc (buf.base , nread);
19191957
1920- session->IncrementCurrentSessionMemory (nread);
1921- OnScopeLeave on_scope_leave ([&]() {
1922- // Once finished handling this write, reset the stream buffer.
1923- // The memory has either been free()d or was handed over to V8.
1924- // We use `nread` instead of `buf.size()` here, because the buffer is
1925- // cleared as part of the `.ToArrayBuffer()` call below.
1926- session->DecrementCurrentSessionMemory (nread);
1958+ session->statistics_ .data_received += nread;
1959+
1960+ if (UNLIKELY (session->stream_buf_offset_ > 0 )) {
1961+ // This is a very unlikely case, and should only happen if the ReadStart()
1962+ // call in OnStreamAfterWrite() immediately provides data. If that does
1963+ // happen, we concatenate the data we received with the already-stored
1964+ // pending input data, slicing off the already processed part.
1965+ char * new_buf = Malloc (
1966+ session->stream_buf_ .len - session->stream_buf_offset_ + nread);
1967+ memcpy (new_buf,
1968+ session->stream_buf_ .base + session->stream_buf_offset_ ,
1969+ session->stream_buf_ .len - session->stream_buf_offset_ );
1970+ memcpy (new_buf + session->stream_buf_ .len - session->stream_buf_offset_ ,
1971+ buf.base ,
1972+ nread);
1973+ free (buf.base );
1974+ nread = session->stream_buf_ .len - session->stream_buf_offset_ + nread;
1975+ buf = uv_buf_init (new_buf, nread);
1976+ session->stream_buf_offset_ = 0 ;
19271977 session->stream_buf_ab_ .Reset ();
1928- free (session->stream_buf_allocation_ .base );
1929- session->stream_buf_allocation_ = uv_buf_init (nullptr , 0 );
1930- session->stream_buf_ = uv_buf_init (nullptr , 0 );
1931- });
1978+ session->DecrementCurrentSessionMemory (session->stream_buf_offset_ );
1979+ }
19321980
1933- // Make sure that there was no read previously active .
1934- CHECK_EQ (session-> stream_buf_ .base , nullptr );
1935- CHECK_EQ ( session->stream_buf_ . len , 0 );
1981+ // Shrink to the actual amount of used data .
1982+ buf. base = Realloc (buf .base , nread );
1983+ session->IncrementCurrentSessionMemory (nread );
19361984
19371985 // Remember the current buffer, so that OnDataChunkReceived knows the
19381986 // offset of a DATA frame's data into the socket read buffer.
@@ -1949,8 +1997,7 @@ void Http2Session::OnStreamReadImpl(ssize_t nread,
19491997 // to copy memory.
19501998 session->stream_buf_allocation_ = buf;
19511999
1952- session->statistics_ .data_received += nread;
1953- ssize_t ret = session->Write (&session->stream_buf_ , 1 );
2000+ ssize_t ret = session->ConsumeHTTP2Data ();
19542001
19552002 if (UNLIKELY (ret < 0 )) {
19562003 DEBUG_HTTP2SESSION2 (session, " fatal error receiving data: %d" , ret);
0 commit comments