9
9
10
10
namespace node {
11
11
12
+ using v8::ArrayBuffer;
12
13
using v8::Boolean ;
13
14
using v8::Context;
14
15
using v8::Float64Array;
@@ -979,7 +980,6 @@ inline int Http2Session::OnStreamClose(nghttp2_session* handle,
979
980
// Intentionally ignore the callback if the stream does not exist or has
980
981
// already been destroyed
981
982
if (stream != nullptr && !stream->IsDestroyed ()) {
982
- stream->AddChunk (nullptr , 0 );
983
983
stream->Close (code);
984
984
// It is possible for the stream close to occur before the stream is
985
985
// ever passed on to the javascript side. If that happens, skip straight
@@ -990,9 +990,8 @@ inline int Http2Session::OnStreamClose(nghttp2_session* handle,
990
990
stream->object ()->Get (context, env->onstreamclose_string ())
991
991
.ToLocalChecked ();
992
992
if (fn->IsFunction ()) {
993
- Local<Value> argv[2 ] = {
994
- Integer::NewFromUnsigned (isolate, code),
995
- Boolean::New (isolate, stream->HasDataChunks (true ))
993
+ Local<Value> argv[] = {
994
+ Integer::NewFromUnsigned (isolate, code)
996
995
};
997
996
stream->MakeCallback (fn.As <Function>(), arraysize (argv), argv);
998
997
} else {
@@ -1029,6 +1028,8 @@ inline int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
1029
1028
Http2Session* session = static_cast <Http2Session*>(user_data);
1030
1029
DEBUG_HTTP2SESSION2 (session, " buffering data chunk for stream %d, size: "
1031
1030
" %d, flags: %d" , id, len, flags);
1031
+ Environment* env = session->env ();
1032
+ HandleScope scope (env->isolate ());
1032
1033
// We should never actually get a 0-length chunk so this check is
1033
1034
// only a precaution at this point.
1034
1035
if (len > 0 ) {
@@ -1040,8 +1041,25 @@ inline int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
1040
1041
// If the stream has been destroyed, ignore this chunk
1041
1042
if (stream->IsDestroyed ())
1042
1043
return 0 ;
1044
+
1043
1045
stream->statistics_ .received_bytes += len;
1044
- stream->AddChunk (data, len);
1046
+
1047
+ // There is a single large array buffer for the entire data read from the
1048
+ // network; create a slice of that array buffer and emit it as the
1049
+ // received data buffer.
1050
+ CHECK (!session->stream_buf_ab_ .IsEmpty ());
1051
+ size_t offset = reinterpret_cast <const char *>(data) - session->stream_buf_ ;
1052
+ // Verify that the data offset is inside the current read buffer.
1053
+ CHECK_LE (offset, session->stream_buf_size_ );
1054
+
1055
+ Local<Object> buf =
1056
+ Buffer::New (env, session->stream_buf_ab_ , offset, len).ToLocalChecked ();
1057
+
1058
+ stream->EmitData (len, buf, Local<Object>());
1059
+ if (!stream->IsReading ())
1060
+ stream->inbound_consumed_data_while_paused_ += len;
1061
+ else
1062
+ nghttp2_session_consume_stream (handle, id, len);
1045
1063
}
1046
1064
return 0 ;
1047
1065
}
@@ -1227,9 +1245,8 @@ inline void Http2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
1227
1245
1228
1246
1229
1247
// Called by OnFrameReceived when a complete DATA frame has been received.
1230
- // If we know that this is the last DATA frame (because the END_STREAM flag
1231
- // is set), then we'll terminate the readable side of the StreamBase. If
1232
- // the StreamBase is flowing, we'll push the chunks of data out to JS land.
1248
+ // If we know that this was the last DATA frame (because the END_STREAM flag
1249
+ // is set), then we'll terminate the readable side of the StreamBase.
1233
1250
inline void Http2Session::HandleDataFrame (const nghttp2_frame* frame) {
1234
1251
int32_t id = GetFrameID (frame);
1235
1252
DEBUG_HTTP2SESSION2 (this , " handling data frame for stream %d" , id);
@@ -1240,11 +1257,8 @@ inline void Http2Session::HandleDataFrame(const nghttp2_frame* frame) {
1240
1257
return ;
1241
1258
1242
1259
if (frame->hd .flags & NGHTTP2_FLAG_END_STREAM) {
1243
- stream->AddChunk ( nullptr , 0 );
1260
+ stream->EmitData (UV_EOF, Local<Object>(), Local<Object>() );
1244
1261
}
1245
-
1246
- if (stream->IsReading ())
1247
- stream->FlushDataChunks ();
1248
1262
}
1249
1263
1250
1264
@@ -1619,45 +1633,67 @@ void Http2Session::OnStreamAllocImpl(size_t suggested_size,
1619
1633
uv_buf_t * buf,
1620
1634
void * ctx) {
1621
1635
Http2Session* session = static_cast <Http2Session*>(ctx);
1622
- buf->base = session->stream_alloc ();
1623
- buf->len = kAllocBufferSize ;
1636
+ CHECK_EQ (session->stream_buf_ , nullptr );
1637
+ CHECK_EQ (session->stream_buf_size_ , 0 );
1638
+ buf->base = session->stream_buf_ = Malloc (suggested_size);
1639
+ buf->len = session->stream_buf_size_ = suggested_size;
1640
+ session->IncrementCurrentSessionMemory (suggested_size);
1624
1641
}
1625
1642
1626
1643
// Callback used to receive inbound data from the i/o stream
1627
1644
void Http2Session::OnStreamReadImpl (ssize_t nread,
1628
- const uv_buf_t * bufs ,
1645
+ const uv_buf_t * buf ,
1629
1646
uv_handle_type pending,
1630
1647
void * ctx) {
1631
1648
Http2Session* session = static_cast <Http2Session*>(ctx);
1632
1649
Http2Scope h2scope (session);
1633
1650
CHECK_NE (session->stream_ , nullptr );
1634
1651
DEBUG_HTTP2SESSION2 (session, " receiving %d bytes" , nread);
1635
- if (nread < 0 ) {
1636
- uv_buf_t tmp_buf;
1637
- tmp_buf.base = nullptr ;
1638
- tmp_buf.len = 0 ;
1639
- session->prev_read_cb_ .fn (nread,
1640
- &tmp_buf,
1641
- pending,
1642
- session->prev_read_cb_ .ctx );
1643
- return ;
1644
- }
1645
- if (bufs->len > 0 ) {
1652
+ if (nread <= 0 ) {
1653
+ free (session->stream_buf_ );
1654
+ if (nread < 0 ) {
1655
+ uv_buf_t tmp_buf = uv_buf_init (nullptr , 0 );
1656
+ session->prev_read_cb_ .fn (nread,
1657
+ &tmp_buf,
1658
+ pending,
1659
+ session->prev_read_cb_ .ctx );
1660
+ }
1661
+ } else {
1646
1662
// Only pass data on if nread > 0
1647
- uv_buf_t buf[] { uv_buf_init ((*bufs).base , nread) };
1663
+
1664
+ // Verify that currently: There is memory allocated into which
1665
+ // the data has been read, and that memory buffer is at least as large
1666
+ // as the amount of data we have read, but we have not yet made an
1667
+ // ArrayBuffer out of it.
1668
+ CHECK_NE (session->stream_buf_ , nullptr );
1669
+ CHECK_EQ (session->stream_buf_ , buf->base );
1670
+ CHECK_EQ (session->stream_buf_size_ , buf->len );
1671
+ CHECK_GE (session->stream_buf_size_ , static_cast <size_t >(nread));
1672
+ CHECK (session->stream_buf_ab_ .IsEmpty ());
1673
+
1674
+ Environment* env = session->env ();
1675
+ Isolate* isolate = env->isolate ();
1676
+ HandleScope scope (isolate);
1677
+ Local<Context> context = env->context ();
1678
+ Context::Scope context_scope (context);
1679
+
1680
+ // Create an array buffer for the read data. DATA frames will be emitted
1681
+ // as slices of this array buffer to avoid having to copy memory.
1682
+ session->stream_buf_ab_ =
1683
+ ArrayBuffer::New (isolate,
1684
+ session->stream_buf_ ,
1685
+ session->stream_buf_size_ ,
1686
+ v8::ArrayBufferCreationMode::kInternalized );
1687
+
1688
+ uv_buf_t buf_ = uv_buf_init (buf->base , nread);
1648
1689
session->statistics_ .data_received += nread;
1649
- ssize_t ret = session->Write (buf , 1 );
1690
+ ssize_t ret = session->Write (&buf_ , 1 );
1650
1691
1651
1692
// Note: if ssize_t is not defined (e.g. on Win32), nghttp2 will typedef
1652
1693
// ssize_t to int. Cast here so that the < 0 check actually works on
1653
1694
// Windows.
1654
1695
if (static_cast <int >(ret) < 0 ) {
1655
1696
DEBUG_HTTP2SESSION2 (session, " fatal error receiving data: %d" , ret);
1656
- Environment* env = session->env ();
1657
- Isolate* isolate = env->isolate ();
1658
- HandleScope scope (isolate);
1659
- Local<Context> context = env->context ();
1660
- Context::Scope context_scope (context);
1661
1697
1662
1698
Local<Value> argv[1 ] = {
1663
1699
Integer::New (isolate, ret),
@@ -1668,6 +1704,13 @@ void Http2Session::OnStreamReadImpl(ssize_t nread,
1668
1704
nghttp2_session_want_read (**session));
1669
1705
}
1670
1706
}
1707
+
1708
+ // Since we are finished handling this write, reset the stream buffer.
1709
+ // The memory has either been free()d or was handed over to V8.
1710
+ session->DecrementCurrentSessionMemory (session->stream_buf_size_ );
1711
+ session->stream_buf_ = nullptr ;
1712
+ session->stream_buf_size_ = 0 ;
1713
+ session->stream_buf_ab_ = Local<ArrayBuffer>();
1671
1714
}
1672
1715
1673
1716
void Http2Session::OnStreamDestructImpl (void * ctx) {
@@ -1782,30 +1825,6 @@ void Http2Stream::OnTrailers(const SubmitTrailers& submit_trailers) {
1782
1825
}
1783
1826
}
1784
1827
1785
- inline bool Http2Stream::HasDataChunks (bool ignore_eos) {
1786
- return data_chunks_.size () > (ignore_eos ? 1 : 0 );
1787
- }
1788
-
1789
- // Appends a chunk of received DATA frame data to this Http2Streams internal
1790
- // queue. Note that we must memcpy each chunk because of the way that nghttp2
1791
- // handles it's internal memory`.
1792
- inline void Http2Stream::AddChunk (const uint8_t * data, size_t len) {
1793
- CHECK (!this ->IsDestroyed ());
1794
- if (this ->statistics_ .first_byte == 0 )
1795
- this ->statistics_ .first_byte = uv_hrtime ();
1796
- if (flags_ & NGHTTP2_STREAM_FLAG_EOS)
1797
- return ;
1798
- char * buf = nullptr ;
1799
- if (len > 0 && data != nullptr ) {
1800
- buf = Malloc<char >(len);
1801
- memcpy (buf, data, len);
1802
- } else if (data == nullptr ) {
1803
- flags_ |= NGHTTP2_STREAM_FLAG_EOS;
1804
- }
1805
- data_chunks_.emplace (uv_buf_init (buf, len));
1806
- }
1807
-
1808
-
1809
1828
inline void Http2Stream::Close (int32_t code) {
1810
1829
CHECK (!this ->IsDestroyed ());
1811
1830
flags_ |= NGHTTP2_STREAM_FLAG_CLOSED;
@@ -1842,13 +1861,6 @@ inline void Http2Stream::Destroy() {
1842
1861
1843
1862
DEBUG_HTTP2STREAM (this , " destroying stream" );
1844
1863
1845
- // Free any remaining incoming data chunks.
1846
- while (!data_chunks_.empty ()) {
1847
- uv_buf_t buf = data_chunks_.front ();
1848
- free (buf.base );
1849
- data_chunks_.pop ();
1850
- }
1851
-
1852
1864
// Wait until the start of the next loop to delete because there
1853
1865
// may still be some pending operations queued for this stream.
1854
1866
env ()->SetImmediate ([](Environment* env, void * data) {
@@ -1874,39 +1886,6 @@ inline void Http2Stream::Destroy() {
1874
1886
}
1875
1887
1876
1888
1877
- // Uses the StreamBase API to push a single chunk of queued inbound DATA
1878
- // to JS land.
1879
- void Http2Stream::OnDataChunk (uv_buf_t * chunk) {
1880
- CHECK (!this ->IsDestroyed ());
1881
- Isolate* isolate = env ()->isolate ();
1882
- HandleScope scope (isolate);
1883
- ssize_t len = -1 ;
1884
- Local<Object> buf;
1885
- if (chunk != nullptr ) {
1886
- len = chunk->len ;
1887
- buf = Buffer::New (isolate, chunk->base , len).ToLocalChecked ();
1888
- }
1889
- EmitData (len, buf, this ->object ());
1890
- }
1891
-
1892
-
1893
- inline void Http2Stream::FlushDataChunks () {
1894
- CHECK (!this ->IsDestroyed ());
1895
- Http2Scope h2scope (this );
1896
- if (!data_chunks_.empty ()) {
1897
- uv_buf_t buf = data_chunks_.front ();
1898
- data_chunks_.pop ();
1899
- if (buf.len > 0 ) {
1900
- CHECK_EQ (nghttp2_session_consume_stream (session_->session (),
1901
- id_, buf.len ), 0 );
1902
- OnDataChunk (&buf);
1903
- } else {
1904
- OnDataChunk (nullptr );
1905
- }
1906
- }
1907
- }
1908
-
1909
-
1910
1889
// Initiates a response on the Http2Stream using data provided via the
1911
1890
// StreamBase Streams API.
1912
1891
inline int Http2Stream::SubmitResponse (nghttp2_nv* nva,
@@ -2013,13 +1992,20 @@ inline Http2Stream* Http2Stream::SubmitPushPromise(nghttp2_nv* nva,
2013
1992
// Switch the StreamBase into flowing mode to begin pushing chunks of data
2014
1993
// out to JS land.
2015
1994
inline int Http2Stream::ReadStart () {
1995
+ Http2Scope h2scope (this );
2016
1996
CHECK (!this ->IsDestroyed ());
2017
1997
flags_ |= NGHTTP2_STREAM_FLAG_READ_START;
2018
1998
flags_ &= ~NGHTTP2_STREAM_FLAG_READ_PAUSED;
2019
1999
2020
- // Flush any queued data chunks immediately out to the JS layer
2021
- FlushDataChunks ();
2022
2000
DEBUG_HTTP2STREAM (this , " reading starting" );
2001
+
2002
+ // Tell nghttp2 about our consumption of the data that was handed
2003
+ // off to JS land.
2004
+ nghttp2_session_consume_stream (session_->session (),
2005
+ id_,
2006
+ inbound_consumed_data_while_paused_);
2007
+ inbound_consumed_data_while_paused_ = 0 ;
2008
+
2023
2009
return 0 ;
2024
2010
}
2025
2011
0 commit comments