HTTP internals: do not open too many sockets when preconnecting

Each pair of (normal request, preconnect request) requires only one
socket. E.g. if there is 1 preconnect request in-flight and 2 normal
requests, we need only 2 sockets in total, and not 3.

Therefore, we need to keep track of whether a request is
preconnecting or a normal one.

Task-number: QTBUG-31594
Change-Id: If92ccc35abadfa6090d64ee92bd466615909c94c
Reviewed-by: Richard J. Moore <rich@kde.org>
This commit is contained in:
Peter Hartmann 2013-06-11 10:45:41 +02:00 committed by The Qt Project
parent a418a544ce
commit 48345e5d3c
7 changed files with 68 additions and 12 deletions

View File

@ -85,6 +85,7 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &host
#ifndef QT_NO_NETWORKPROXY #ifndef QT_NO_NETWORKPROXY
, networkProxy(QNetworkProxy::NoProxy) , networkProxy(QNetworkProxy::NoProxy)
#endif #endif
, preConnectRequests(0)
{ {
channels = new QHttpNetworkConnectionChannel[channelCount]; channels = new QHttpNetworkConnectionChannel[channelCount];
} }
@ -96,6 +97,7 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 channelCoun
#ifndef QT_NO_NETWORKPROXY #ifndef QT_NO_NETWORKPROXY
, networkProxy(QNetworkProxy::NoProxy) , networkProxy(QNetworkProxy::NoProxy)
#endif #endif
, preConnectRequests(0)
{ {
channels = new QHttpNetworkConnectionChannel[channelCount]; channels = new QHttpNetworkConnectionChannel[channelCount];
} }
@ -541,6 +543,9 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetwor
reply->d_func()->connectionChannel = &channels[0]; // will have the correct one set later reply->d_func()->connectionChannel = &channels[0]; // will have the correct one set later
HttpMessagePair pair = qMakePair(request, reply); HttpMessagePair pair = qMakePair(request, reply);
if (request.isPreConnect())
preConnectRequests++;
switch (request.priority()) { switch (request.priority()) {
case QHttpNetworkRequest::HighPriority: case QHttpNetworkRequest::HighPriority:
highPriorityQueue.prepend(pair); highPriorityQueue.prepend(pair);
@ -925,15 +930,24 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
// If there is not already any connected channels we need to connect a new one. // If there is not already any connected channels we need to connect a new one.
// We do not pair the channel with the request until we know if it is // We do not pair the channel with the request until we know if it is
// connected or not. This is to reuse connected channels before we connect new once. // connected or not. This is to reuse connected channels before we connect new once.
int queuedRequest = highPriorityQueue.count() + lowPriorityQueue.count(); int queuedRequests = highPriorityQueue.count() + lowPriorityQueue.count();
for (int i = 0; i < channelCount; ++i) {
// in case we have in-flight preconnect requests and normal requests,
// we only need one socket for each (preconnect, normal request) pair
int neededOpenChannels = queuedRequests;
if (preConnectRequests > 0) {
int normalRequests = queuedRequests - preConnectRequests;
neededOpenChannels = qMax(normalRequests, preConnectRequests);
}
for (int i = 0; i < channelCount && neededOpenChannels > 0; ++i) {
bool connectChannel = false; bool connectChannel = false;
if (channels[i].socket) { if (channels[i].socket) {
if ((channels[i].socket->state() == QAbstractSocket::ConnectingState) if ((channels[i].socket->state() == QAbstractSocket::ConnectingState)
|| (channels[i].socket->state() == QAbstractSocket::HostLookupState) || (channels[i].socket->state() == QAbstractSocket::HostLookupState)
|| channels[i].pendingEncrypt) // pendingEncrypt == "EncryptingState" || channels[i].pendingEncrypt) // pendingEncrypt == "EncryptingState"
queuedRequest--; neededOpenChannels--;
if ( queuedRequest <=0 )
if (neededOpenChannels <= 0)
break; break;
if (!channels[i].reply && !channels[i].isSocketBusy() && (channels[i].socket->state() == QAbstractSocket::UnconnectedState)) if (!channels[i].reply && !channels[i].isSocketBusy() && (channels[i].socket->state() == QAbstractSocket::UnconnectedState))
connectChannel = true; connectChannel = true;
@ -947,11 +961,8 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
else if (networkLayerState == IPv6) else if (networkLayerState == IPv6)
channels[i].networkLayerPreference = QAbstractSocket::IPv6Protocol; channels[i].networkLayerPreference = QAbstractSocket::IPv6Protocol;
channels[i].ensureConnection(); channels[i].ensureConnection();
queuedRequest--; neededOpenChannels--;
} }
if ( queuedRequest <=0 )
break;
} }
} }
@ -1272,6 +1283,11 @@ void QHttpNetworkConnection::ignoreSslErrors(const QList<QSslError> &errors, int
#endif //QT_NO_SSL #endif //QT_NO_SSL
void QHttpNetworkConnection::preConnectFinished()
{
d_func()->preConnectRequests--;
}
#ifndef QT_NO_NETWORKPROXY #ifndef QT_NO_NETWORKPROXY
// only called from QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired, not // only called from QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired, not
// from QHttpNetworkConnectionChannel::handleAuthenticationChallenge // from QHttpNetworkConnectionChannel::handleAuthenticationChallenge

View File

@ -131,6 +131,8 @@ public:
void setSslContext(QSharedPointer<QSslContext> context); void setSslContext(QSharedPointer<QSslContext> context);
#endif #endif
void preConnectFinished();
private: private:
Q_DECLARE_PRIVATE(QHttpNetworkConnection) Q_DECLARE_PRIVATE(QHttpNetworkConnection)
Q_DISABLE_COPY(QHttpNetworkConnection) Q_DISABLE_COPY(QHttpNetworkConnection)
@ -239,6 +241,8 @@ public:
QList<HttpMessagePair> highPriorityQueue; QList<HttpMessagePair> highPriorityQueue;
QList<HttpMessagePair> lowPriorityQueue; QList<HttpMessagePair> lowPriorityQueue;
int preConnectRequests;
#ifndef QT_NO_SSL #ifndef QT_NO_SSL
QSharedPointer<QSslContext> sslContext; QSharedPointer<QSslContext> sslContext;
#endif #endif

View File

@ -214,6 +214,7 @@ bool QHttpNetworkConnectionChannel::sendRequest()
state = QHttpNetworkConnectionChannel::IdleState; state = QHttpNetworkConnectionChannel::IdleState;
reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState; reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
allDone(); allDone();
connection->preConnectFinished(); // will only decrease the counter
reply = 0; // so we can reuse this channel reply = 0; // so we can reuse this channel
return true; // we have a working connection and are done return true; // we have a working connection and are done
} }

View File

@ -49,7 +49,8 @@ QT_BEGIN_NAMESPACE
QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(QHttpNetworkRequest::Operation op, QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(QHttpNetworkRequest::Operation op,
QHttpNetworkRequest::Priority pri, const QUrl &newUrl) QHttpNetworkRequest::Priority pri, const QUrl &newUrl)
: QHttpNetworkHeaderPrivate(newUrl), operation(op), priority(pri), uploadByteDevice(0), : QHttpNetworkHeaderPrivate(newUrl), operation(op), priority(pri), uploadByteDevice(0),
autoDecompress(false), pipeliningAllowed(false), withCredentials(true) autoDecompress(false), pipeliningAllowed(false), withCredentials(true),
preConnect(false)
{ {
} }
@ -64,6 +65,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest
customVerb = other.customVerb; customVerb = other.customVerb;
withCredentials = other.withCredentials; withCredentials = other.withCredentials;
ssl = other.ssl; ssl = other.ssl;
preConnect = other.preConnect;
} }
QHttpNetworkRequestPrivate::~QHttpNetworkRequestPrivate() QHttpNetworkRequestPrivate::~QHttpNetworkRequestPrivate()
@ -75,7 +77,8 @@ bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &ot
return QHttpNetworkHeaderPrivate::operator==(other) return QHttpNetworkHeaderPrivate::operator==(other)
&& (operation == other.operation) && (operation == other.operation)
&& (ssl == other.ssl) && (ssl == other.ssl)
&& (uploadByteDevice == other.uploadByteDevice); && (uploadByteDevice == other.uploadByteDevice)
&& (preConnect == other.preConnect);
} }
QByteArray QHttpNetworkRequestPrivate::methodName() const QByteArray QHttpNetworkRequestPrivate::methodName() const
@ -205,6 +208,15 @@ void QHttpNetworkRequest::setSsl(bool s)
d->ssl = s; d->ssl = s;
} }
bool QHttpNetworkRequest::isPreConnect() const
{
return d->preConnect;
}
void QHttpNetworkRequest::setPreConnect(bool preConnect)
{
d->preConnect = preConnect;
}
qint64 QHttpNetworkRequest::contentLength() const qint64 QHttpNetworkRequest::contentLength() const
{ {
return d->contentLength(); return d->contentLength();

View File

@ -120,6 +120,9 @@ public:
bool isSsl() const; bool isSsl() const;
void setSsl(bool); void setSsl(bool);
bool isPreConnect() const;
void setPreConnect(bool preConnect);
void setUploadByteDevice(QNonContiguousByteDevice *bd); void setUploadByteDevice(QNonContiguousByteDevice *bd);
QNonContiguousByteDevice* uploadByteDevice() const; QNonContiguousByteDevice* uploadByteDevice() const;
@ -151,6 +154,7 @@ public:
bool pipeliningAllowed; bool pipeliningAllowed;
bool withCredentials; bool withCredentials;
bool ssl; bool ssl;
bool preConnect;
}; };

View File

@ -635,6 +635,9 @@ void QNetworkReplyHttpImplPrivate::postRequest()
q->setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, ssl); q->setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, ssl);
httpRequest.setSsl(ssl); httpRequest.setSsl(ssl);
bool preConnect = (scheme == QLatin1String("preconnect-http")
|| scheme == QLatin1String("preconnect-https"));
httpRequest.setPreConnect(preConnect);
#ifndef QT_NO_NETWORKPROXY #ifndef QT_NO_NETWORKPROXY
QNetworkProxy transparentProxy, cacheProxy; QNetworkProxy transparentProxy, cacheProxy;

View File

@ -463,6 +463,7 @@ private slots:
#ifndef QT_NO_SSL #ifndef QT_NO_SSL
void echoPerformance_data(); void echoPerformance_data();
void echoPerformance(); void echoPerformance();
void preConnectEncrypted_data();
void preConnectEncrypted(); void preConnectEncrypted();
#endif #endif
@ -476,6 +477,7 @@ private slots:
void httpDownloadPerformanceDownloadBuffer(); void httpDownloadPerformanceDownloadBuffer();
void httpsRequestChain(); void httpsRequestChain();
void httpsUpload(); void httpsUpload();
void preConnect_data();
void preConnect(); void preConnect();
private: private:
@ -548,6 +550,13 @@ void tst_qnetworkreply::echoPerformance()
} }
} }
void tst_qnetworkreply::preConnectEncrypted_data()
{
QTest::addColumn<int>("sleepTime");
QTest::newRow("2secs") << 2000; // to start a new request after preconnecting is done
QTest::newRow("100ms") << 100; // to start a new request while preconnecting is in-flight
}
void tst_qnetworkreply::preConnectEncrypted() void tst_qnetworkreply::preConnectEncrypted()
{ {
QString hostName = QLatin1String("www.google.com"); QString hostName = QLatin1String("www.google.com");
@ -579,8 +588,9 @@ void tst_qnetworkreply::preConnectEncrypted()
manager.clearAccessCache(); manager.clearAccessCache();
// now try to make the connection beforehand // now try to make the connection beforehand
QFETCH(int, sleepTime);
manager.connectToHostEncrypted(hostName); manager.connectToHostEncrypted(hostName);
QTestEventLoop::instance().enterLoop(2); QTestEventLoop::instance().enterLoopMSecs(sleepTime);
// now make another request and hopefully use the existing connection // now make another request and hopefully use the existing connection
QPair<QNetworkReply *, qint64> preConnectResult = runGetRequest(&manager, request); QPair<QNetworkReply *, qint64> preConnectResult = runGetRequest(&manager, request);
@ -917,6 +927,11 @@ void tst_qnetworkreply::httpsUpload()
} }
} }
void tst_qnetworkreply::preConnect_data()
{
preConnectEncrypted_data();
}
void tst_qnetworkreply::preConnect() void tst_qnetworkreply::preConnect()
{ {
QString hostName = QLatin1String("www.google.com"); QString hostName = QLatin1String("www.google.com");
@ -948,8 +963,9 @@ void tst_qnetworkreply::preConnect()
manager.clearAccessCache(); manager.clearAccessCache();
// now try to make the connection beforehand // now try to make the connection beforehand
QFETCH(int, sleepTime);
manager.connectToHost(hostName); manager.connectToHost(hostName);
QTestEventLoop::instance().enterLoop(2); QTestEventLoop::instance().enterLoopMSecs(sleepTime);
// now make another request and hopefully use the existing connection // now make another request and hopefully use the existing connection
QPair<QNetworkReply *, qint64> preConnectResult = runGetRequest(&manager, request); QPair<QNetworkReply *, qint64> preConnectResult = runGetRequest(&manager, request);