@@ -918,31 +918,51 @@ inline ssize_t Http2Session::OnCallbackPadding(size_t frameLen,
918
918
// various callback functions. Each of these will typically result in a call
919
919
// out to JavaScript so this particular function is rather hot and can be
920
920
// 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);
938
942
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
+ }
940
948
}
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
+
941
961
// Send any data that was queued up while processing the received data.
942
962
if (!IsDestroyed ()) {
943
963
SendPendingData ();
944
964
}
945
- return total ;
965
+ return ret ;
946
966
}
947
967
948
968
@@ -1238,6 +1258,7 @@ inline int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
1238
1258
size_t offset = data - reinterpret_cast <uint8_t *>(session->stream_buf_ .base );
1239
1259
1240
1260
// Verify that the data offset is inside the current read buffer.
1261
+ CHECK_GE (offset, session->stream_buf_offset_ );
1241
1262
CHECK_LE (offset, session->stream_buf_ .len );
1242
1263
CHECK_LE (offset + len, session->stream_buf_ .len );
1243
1264
@@ -1250,6 +1271,16 @@ inline int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
1250
1271
else
1251
1272
nghttp2_session_consume_stream (handle, id, len);
1252
1273
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
+
1253
1284
return 0 ;
1254
1285
}
1255
1286
@@ -1597,6 +1628,11 @@ void Http2Session::OnStreamAfterWriteImpl(WriteWrap* w, int status, void* ctx) {
1597
1628
session->stream_ ->ReadStart ();
1598
1629
}
1599
1630
1631
+ // If there is more incoming data queued up, consume it.
1632
+ if (session->stream_buf_offset_ > 0 ) {
1633
+ session->ConsumeHTTP2Data ();
1634
+ }
1635
+
1600
1636
if (!(session->flags_ & SESSION_STATE_WRITE_SCHEDULED)) {
1601
1637
// Schedule a new write if nghttp2 wants to send data.
1602
1638
session->MaybeScheduleWrite ();
@@ -1654,6 +1690,7 @@ void Http2Session::ClearOutgoing(int status) {
1654
1690
1655
1691
if (outgoing_buffers_.size () > 0 ) {
1656
1692
outgoing_storage_.clear ();
1693
+ outgoing_length_ = 0 ;
1657
1694
1658
1695
std::vector<nghttp2_stream_write> current_outgoing_buffers_;
1659
1696
current_outgoing_buffers_.swap (outgoing_buffers_);
@@ -1680,6 +1717,11 @@ void Http2Session::ClearOutgoing(int status) {
1680
1717
}
1681
1718
}
1682
1719
1720
+ void Http2Session::PushOutgoingBuffer (nghttp2_stream_write&& write) {
1721
+ outgoing_length_ += write .buf .len ;
1722
+ outgoing_buffers_.emplace_back (std::move (write ));
1723
+ }
1724
+
1683
1725
// Queue a given block of data for sending. This always creates a copy,
1684
1726
// so it is used for the cases in which nghttp2 requests sending of a
1685
1727
// small chunk of data.
@@ -1692,7 +1734,7 @@ void Http2Session::CopyDataIntoOutgoing(const uint8_t* src, size_t src_length) {
1692
1734
// of the outgoing_buffers_ vector may invalidate the pointer.
1693
1735
// The correct base pointers will be set later, before writing to the
1694
1736
// underlying socket.
1695
- outgoing_buffers_. emplace_back (nghttp2_stream_write {
1737
+ PushOutgoingBuffer (nghttp2_stream_write {
1696
1738
uv_buf_init (nullptr , src_length)
1697
1739
});
1698
1740
}
@@ -1826,13 +1868,13 @@ int Http2Session::OnSendData(
1826
1868
if (write .buf .len <= length) {
1827
1869
// This write does not suffice by itself, so we can consume it completely.
1828
1870
length -= write .buf .len ;
1829
- session->outgoing_buffers_ . emplace_back (std::move (write ));
1871
+ session->PushOutgoingBuffer (std::move (write ));
1830
1872
stream->queue_ .pop ();
1831
1873
continue ;
1832
1874
}
1833
1875
1834
1876
// 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 {
1836
1878
uv_buf_init (write .buf .base , length)
1837
1879
});
1838
1880
write .buf .base += length;
@@ -1842,7 +1884,7 @@ int Http2Session::OnSendData(
1842
1884
1843
1885
if (frame->data .padlen > 0 ) {
1844
1886
// Send padding if that was requested.
1845
- session->outgoing_buffers_ . emplace_back (nghttp2_stream_write {
1887
+ session->PushOutgoingBuffer (nghttp2_stream_write {
1846
1888
uv_buf_init (const_cast <char *>(zero_bytes_256), frame->data .padlen - 1 )
1847
1889
});
1848
1890
}
@@ -1896,8 +1938,6 @@ void Http2Session::OnStreamReadImpl(ssize_t nread,
1896
1938
Http2Scope h2scope (session);
1897
1939
CHECK_NE (session->stream_ , nullptr );
1898
1940
DEBUG_HTTP2SESSION2 (session, " receiving %d bytes" , nread);
1899
- CHECK_EQ (session->stream_buf_allocation_ .base , nullptr );
1900
- CHECK (session->stream_buf_ab_ .IsEmpty ());
1901
1941
1902
1942
// Only pass data on if nread > 0
1903
1943
if (nread <= 0 ) {
@@ -1913,26 +1953,34 @@ void Http2Session::OnStreamReadImpl(ssize_t nread,
1913
1953
return ;
1914
1954
}
1915
1955
1916
- // Shrink to the actual amount of used data.
1917
1956
uv_buf_t buf = *buf_;
1918
- buf.base = Realloc (buf.base , nread);
1919
1957
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 ;
1927
1977
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
+ }
1932
1980
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 );
1936
1984
1937
1985
// Remember the current buffer, so that OnDataChunkReceived knows the
1938
1986
// offset of a DATA frame's data into the socket read buffer.
@@ -1949,8 +1997,7 @@ void Http2Session::OnStreamReadImpl(ssize_t nread,
1949
1997
// to copy memory.
1950
1998
session->stream_buf_allocation_ = buf;
1951
1999
1952
- session->statistics_ .data_received += nread;
1953
- ssize_t ret = session->Write (&session->stream_buf_ , 1 );
2000
+ ssize_t ret = session->ConsumeHTTP2Data ();
1954
2001
1955
2002
if (UNLIKELY (ret < 0 )) {
1956
2003
DEBUG_HTTP2SESSION2 (session, " fatal error receiving data: %d" , ret);
0 commit comments