HTTP/2 protocol handler: tweak receive window sizes
We were using the default ones, provided by RFC7540. It appears they are way too restrictive and conservative: when downloading something relatively big, a stream keeps spending the whole session/its own 'recv' windows and thus we have to constantly send WINDOW_UPDATE frames. This significantly slows down our HTTP/2 implementation, making it orders of magnitude slower than HTTP/1.1. To fix this: - We send SETTINGS_INITIAL_WINDOW_SIZE in the first SETTINGS frame to inform our peer that per-stream WINDOW is bigger than 64Kb - We increase the session's receive window size. Task-number: QTBUG-63722 Change-Id: I31312fcfd5f0fc0aee6aaa5d3562cc7d1b931adc Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>bb10
parent
65eed6d597
commit
a520c179b8
|
|
@ -216,6 +216,8 @@ Frame default_SETTINGS_frame()
|
|||
// MAX frame size (16 kb), disable/enable PUSH_PROMISE
|
||||
builder.append(Settings::MAX_FRAME_SIZE_ID);
|
||||
builder.append(quint32(maxFrameSize));
|
||||
builder.append(Settings::INITIAL_WINDOW_SIZE_ID);
|
||||
builder.append(initialStreamReceiveWindowSize);
|
||||
builder.append(Settings::ENABLE_PUSH_ID);
|
||||
builder.append(quint32(is_PUSH_PROMISE_enabled()));
|
||||
|
||||
|
|
|
|||
|
|
@ -129,10 +129,20 @@ enum Http2PredefinedParameters
|
|||
maxConcurrentStreams = 100 // HTTP/2, 6.5.2
|
||||
};
|
||||
|
||||
// It's int, it has internal linkage, it's ok to have it in headers -
|
||||
// no ODR violation is possible.
|
||||
// These are ints, const, they have internal linkage, it's ok to have them in
|
||||
// headers - no ODR violation.
|
||||
const quint32 lastValidStreamID((quint32(1) << 31) - 1); // HTTP/2, 5.1.1
|
||||
|
||||
// The default size of 64K is too small and limiting: if we use it, we end up
|
||||
// sending WINDOW_UPDATE frames on a stream/session all the time, for each
|
||||
// 2 DATE frames of size 16K (also default) we'll send a WINDOW_UPDATE frame
|
||||
// for a given stream and have a download speed order of magnitude lower than
|
||||
// our own HTTP/1.1 protocol handler. We choose a bigger window size (normally,
|
||||
// HTTP/2 servers are not afraid to immediately set it to possible max anyway)
|
||||
// and split this window size between our concurrent streams.
|
||||
const qint32 initialSessionReceiveWindowSize = defaultSessionWindowSize * 10000;
|
||||
const qint32 initialStreamReceiveWindowSize = initialSessionReceiveWindowSize / maxConcurrentStreams;
|
||||
|
||||
extern const Q_AUTOTEST_EXPORT char Http2clientPreface[clientPrefaceLength];
|
||||
void prepare_for_protocol_upgrade(QHttpNetworkRequest &request);
|
||||
|
||||
|
|
|
|||
|
|
@ -161,8 +161,6 @@ bool sum_will_overflow(qint32 windowSize, qint32 delta)
|
|||
using namespace Http2;
|
||||
|
||||
const std::deque<quint32>::size_type QHttp2ProtocolHandler::maxRecycledStreams = 10000;
|
||||
const qint32 QHttp2ProtocolHandler::sessionMaxRecvWindowSize;
|
||||
const qint32 QHttp2ProtocolHandler::streamInitialRecvWindowSize;
|
||||
const quint32 QHttp2ProtocolHandler::maxAcceptableTableSize;
|
||||
|
||||
QHttp2ProtocolHandler::QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *channel)
|
||||
|
|
@ -374,12 +372,10 @@ bool QHttp2ProtocolHandler::sendClientPreface()
|
|||
if (!frameWriter.write(*m_socket))
|
||||
return false;
|
||||
|
||||
sessionRecvWindowSize = sessionMaxRecvWindowSize;
|
||||
if (defaultSessionWindowSize < sessionMaxRecvWindowSize) {
|
||||
const auto delta = sessionMaxRecvWindowSize - defaultSessionWindowSize;
|
||||
if (!sendWINDOW_UPDATE(connectionStreamID, delta))
|
||||
return false;
|
||||
}
|
||||
sessionRecvWindowSize = Http2::initialSessionReceiveWindowSize;
|
||||
const auto delta = Http2::initialSessionReceiveWindowSize - Http2::defaultSessionWindowSize;
|
||||
if (!sendWINDOW_UPDATE(Http2::connectionStreamID, delta))
|
||||
return false;
|
||||
|
||||
prefaceSent = true;
|
||||
waitingForSettingsACK = true;
|
||||
|
|
@ -549,20 +545,20 @@ void QHttp2ProtocolHandler::handleDATA()
|
|||
if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM)) {
|
||||
finishStream(stream);
|
||||
deleteActiveStream(stream.streamID);
|
||||
} else if (stream.recvWindow < streamInitialRecvWindowSize / 2) {
|
||||
} else if (stream.recvWindow < Http2::initialStreamReceiveWindowSize / 2) {
|
||||
QMetaObject::invokeMethod(this, "sendWINDOW_UPDATE", Qt::QueuedConnection,
|
||||
Q_ARG(quint32, stream.streamID),
|
||||
Q_ARG(quint32, streamInitialRecvWindowSize - stream.recvWindow));
|
||||
stream.recvWindow = streamInitialRecvWindowSize;
|
||||
Q_ARG(quint32, Http2::initialStreamReceiveWindowSize - stream.recvWindow));
|
||||
stream.recvWindow = Http2::initialStreamReceiveWindowSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sessionRecvWindowSize < sessionMaxRecvWindowSize / 2) {
|
||||
if (sessionRecvWindowSize < Http2::initialSessionReceiveWindowSize / 2) {
|
||||
QMetaObject::invokeMethod(this, "sendWINDOW_UPDATE", Qt::QueuedConnection,
|
||||
Q_ARG(quint32, connectionStreamID),
|
||||
Q_ARG(quint32, sessionMaxRecvWindowSize - sessionRecvWindowSize));
|
||||
sessionRecvWindowSize = sessionMaxRecvWindowSize;
|
||||
Q_ARG(quint32, Http2::initialSessionReceiveWindowSize - sessionRecvWindowSize));
|
||||
sessionRecvWindowSize = Http2::initialSessionReceiveWindowSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1203,7 +1199,7 @@ quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message, b
|
|||
|
||||
const Stream newStream(message, newStreamID,
|
||||
streamInitialSendWindowSize,
|
||||
streamInitialRecvWindowSize);
|
||||
Http2::initialStreamReceiveWindowSize);
|
||||
|
||||
if (!uploadDone) {
|
||||
if (auto src = newStream.data()) {
|
||||
|
|
@ -1398,7 +1394,8 @@ bool QHttp2ProtocolHandler::tryReserveStream(const Http2::Frame &pushPromiseFram
|
|||
promise.reservedID = reservedID;
|
||||
promise.pushHeader = requestHeader;
|
||||
|
||||
activeStreams.insert(reservedID, Stream(urlKey, reservedID, streamInitialRecvWindowSize));
|
||||
activeStreams.insert(reservedID, Stream(urlKey, reservedID,
|
||||
Http2::initialStreamReceiveWindowSize));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1432,7 +1429,7 @@ void QHttp2ProtocolHandler::initReplyFromPushPromise(const HttpMessagePair &mess
|
|||
// Let's pretent we're sending a request now:
|
||||
Stream closedStream(message, promise.reservedID,
|
||||
streamInitialSendWindowSize,
|
||||
streamInitialRecvWindowSize);
|
||||
Http2::initialStreamReceiveWindowSize);
|
||||
closedStream.state = Stream::halfClosedLocal;
|
||||
activeStreams.insert(promise.reservedID, closedStream);
|
||||
promisedStream = &activeStreams[promise.reservedID];
|
||||
|
|
|
|||
|
|
@ -176,13 +176,9 @@ private:
|
|||
quint32 maxConcurrentStreams = Http2::maxConcurrentStreams;
|
||||
|
||||
// Control flow:
|
||||
static const qint32 sessionMaxRecvWindowSize = Http2::defaultSessionWindowSize * 10;
|
||||
// Signed integer, it can become negative (it's still a valid window size):
|
||||
qint32 sessionRecvWindowSize = sessionMaxRecvWindowSize;
|
||||
|
||||
// We do not negotiate this window size
|
||||
// We have to send WINDOW_UPDATE frames to our peer also.
|
||||
static const qint32 streamInitialRecvWindowSize = Http2::defaultSessionWindowSize;
|
||||
// Signed integer, it can become negative (it's still a valid window size):
|
||||
qint32 sessionRecvWindowSize = Http2::initialSessionReceiveWindowSize;
|
||||
|
||||
// Updated by SETTINGS and WINDOW_UPDATE.
|
||||
qint32 sessionSendWindowSize = Http2::defaultSessionWindowSize;
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ private:
|
|||
|
||||
const Http2Settings tst_Http2::defaultServerSettings{{Http2::Settings::MAX_CONCURRENT_STREAMS_ID, 100}};
|
||||
const Http2Settings tst_Http2::defaultClientSettings{{Http2::Settings::MAX_FRAME_SIZE_ID, quint32(Http2::maxFrameSize)},
|
||||
{Http2::Settings::INITIAL_WINDOW_SIZE_ID, quint32(Http2::initialStreamReceiveWindowSize)},
|
||||
{Http2::Settings::ENABLE_PUSH_ID, quint32(0)}};
|
||||
|
||||
namespace {
|
||||
|
|
@ -274,7 +275,7 @@ void tst_Http2::flowControlClientSide()
|
|||
|
||||
ServerPtr srv(newServer(serverSettings));
|
||||
|
||||
const QByteArray respond(int(Http2::defaultSessionWindowSize * 50), 'x');
|
||||
const QByteArray respond(int(Http2::initialStreamReceiveWindowSize * 5), 'x');
|
||||
srv->setResponseBody(respond);
|
||||
|
||||
QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection);
|
||||
|
|
@ -342,6 +343,7 @@ void tst_Http2::pushPromise()
|
|||
|
||||
const EnvVarGuard env("QT_HTTP2_ENABLE_PUSH_PROMISE", "1");
|
||||
const Http2Settings clientSettings{{Settings::MAX_FRAME_SIZE_ID, quint32(Http2::maxFrameSize)},
|
||||
{Http2::Settings::INITIAL_WINDOW_SIZE_ID, quint32(Http2::initialStreamReceiveWindowSize)},
|
||||
{Settings::ENABLE_PUSH_ID, quint32(1)}};
|
||||
|
||||
ServerPtr srv(newServer(defaultServerSettings, clientSettings));
|
||||
|
|
|
|||
Loading…
Reference in New Issue