diff options
author | Markus Goetz <markus@woboq.com> | 2021-06-14 17:40:06 +0200 |
---|---|---|
committer | Markus Goetz <markus@woboq.com> | 2021-07-27 17:16:58 +0200 |
commit | 85cfbae1d62617fdc452680813f993e812bb55dd (patch) | |
tree | 647525ccc36d6b3ff707f206869f882ff104a4c3 /src | |
parent | 5597e26256f37168b1da2bf8b6c1a9ab7ab2618c (diff) |
QNAM: Allow to configure when connections to a host are torn down
This introduces a new attribute that allows behavior to keep
the TCP connection(s) to a HTTP1/HTTP2 host longer or shorter
than the default of 120 seconds.
Note that the server might still close the connection earlier.
Fixes: QTBUG-20726
Fixes: QTBUG-91440
Change-Id: I7da64230a78c642c12c0ddbe6b678cf17c3aafde
Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/network/access/qhttpthreaddelegate.cpp | 3 | ||||
-rw-r--r-- | src/network/access/qhttpthreaddelegate_p.h | 1 | ||||
-rw-r--r-- | src/network/access/qnetworkaccesscache.cpp | 59 | ||||
-rw-r--r-- | src/network/access/qnetworkaccesscache_p.h | 3 | ||||
-rw-r--r-- | src/network/access/qnetworkreplyhttpimpl.cpp | 3 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.cpp | 6 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.h | 1 |
7 files changed, 62 insertions, 14 deletions
diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp index 8db56222d2..50a14b6258 100644 --- a/src/network/access/qhttpthreaddelegate.cpp +++ b/src/network/access/qhttpthreaddelegate.cpp @@ -226,6 +226,7 @@ QHttpThreadDelegate::QHttpThreadDelegate(QObject *parent) : , pendingDownloadData() , pendingDownloadProgress() , synchronous(false) + , connectionCacheExpiryTimeoutSeconds(-1) , incomingStatusCode(0) , isPipeliningUsed(false) , isHttp2Used(false) @@ -344,7 +345,7 @@ void QHttpThreadDelegate::startRequest() #endif httpConnection->setPeerVerifyName(httpRequest.peerVerifyName()); // cache the QHttpNetworkConnection corresponding to this cache key - connections.localData()->addEntry(cacheKey, httpConnection); + connections.localData()->addEntry(cacheKey, httpConnection, connectionCacheExpiryTimeoutSeconds); } else { if (httpRequest.withCredentials()) { QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), nullptr); diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h index f919b403f9..f0b9b8edf3 100644 --- a/src/network/access/qhttpthreaddelegate_p.h +++ b/src/network/access/qhttpthreaddelegate_p.h @@ -105,6 +105,7 @@ public: #endif QSharedPointer<QNetworkAccessAuthenticationManager> authenticationManager; bool synchronous; + qint64 connectionCacheExpiryTimeoutSeconds; // outgoing, Retrieved in the synchronous HTTP case QByteArray synchronousDownloadData; diff --git a/src/network/access/qnetworkaccesscache.cpp b/src/network/access/qnetworkaccesscache.cpp index 4d65761a0b..7c8da551ad 100644 --- a/src/network/access/qnetworkaccesscache.cpp +++ b/src/network/access/qnetworkaccesscache.cpp @@ -148,18 +148,46 @@ void QNetworkAccessCache::linkEntry(const QByteArray &key) Q_ASSERT(node->older == nullptr && node->newer == nullptr); Q_ASSERT(node->useCount == 0); + + node->timestamp = QDateTime::currentDateTimeUtc().addSecs(node->object->expiryTimeoutSeconds); +#ifdef QT_DEBUG + qDebug() << "QNetworkAccessCache case trying to insert=" <<QString::fromUtf8(key) << node->timestamp; + Node *current = newest; + while (current) { + qDebug() << "QNetworkAccessCache item=" << QString::fromUtf8(current->key) << current->timestamp << (current==newest? "newest":"") << (current==oldest? "oldest":""); + current = current->older; + } +#endif + if (newest) { Q_ASSERT(newest->newer == nullptr); - newest->newer = node; - node->older = newest; + if (newest->timestamp < node->timestamp) { + // Insert as new newest. + node->older = newest; + newest->newer = node; + newest = node; + Q_ASSERT(newest->newer == nullptr); + } else { + // Insert in a sorted way, as different nodes might have had different expiryTimeoutSeconds set. + Node *current = newest; + while (current->older != nullptr && current->older->timestamp >= node->timestamp) { + current = current->older; + } + node->older = current->older; + current->older = node; + if (node->older == nullptr) { + oldest = node; + Q_ASSERT(oldest->older == nullptr); + } + } + } else { + // no newest yet + newest = node; } if (!oldest) { // there are no entries, so this is the oldest one too oldest = node; } - - node->timestamp = QDateTime::currentDateTimeUtc().addSecs(ExpiryTime); - newest = node; } /*! @@ -195,15 +223,16 @@ void QNetworkAccessCache::updateTimer() if (!oldest) return; - int interval = QDateTime::currentDateTimeUtc().secsTo(oldest->timestamp); + qint64 interval = QDateTime::currentDateTimeUtc().msecsTo(oldest->timestamp); if (interval <= 0) { interval = 0; - } else { - // round up the interval - interval = (interval + 15) & ~16; } - timer.start(interval * 1000, this); + // Plus 10 msec so we don't spam timer events if date comparisons are too fuzzy. + // This code used to do (broken) rounding, but for ConnectionCacheExpiryTimeoutSecondsAttribute + // to work we cannot do this. + // See discussion in https://codereview.qt-project.org/c/qt/qtbase/+/337464 + timer.start(interval + 10, this); } bool QNetworkAccessCache::emitEntryReady(Node *node, QObject *target, const char *member) @@ -226,7 +255,6 @@ void QNetworkAccessCache::timerEvent(QTimerEvent *) while (oldest && oldest->timestamp < now) { Node *next = oldest->newer; oldest->object->dispose(); - hash.remove(oldest->key); // oldest gets deleted delete oldest; oldest = next; @@ -241,7 +269,7 @@ void QNetworkAccessCache::timerEvent(QTimerEvent *) updateTimer(); } -void QNetworkAccessCache::addEntry(const QByteArray &key, CacheableObject *entry) +void QNetworkAccessCache::addEntry(const QByteArray &key, CacheableObject *entry, qint64 connectionCacheExpiryTimeoutSeconds) { Q_ASSERT(!key.isEmpty()); @@ -260,8 +288,15 @@ void QNetworkAccessCache::addEntry(const QByteArray &key, CacheableObject *entry node->object->dispose(); node->object = entry; node->object->key = key; + if (connectionCacheExpiryTimeoutSeconds > -1) { + node->object->expiryTimeoutSeconds = connectionCacheExpiryTimeoutSeconds; // via ConnectionCacheExpiryTimeoutSecondsAttribute + } else { + node->object->expiryTimeoutSeconds = ExpiryTime; + } node->key = key; node->useCount = 1; + + // It gets only put into the expiry list in linkEntry (from releaseEntry), when it is not used anymore. } bool QNetworkAccessCache::hasEntry(const QByteArray &key) const diff --git a/src/network/access/qnetworkaccesscache_p.h b/src/network/access/qnetworkaccesscache_p.h index 9f7001d044..e31722e6fd 100644 --- a/src/network/access/qnetworkaccesscache_p.h +++ b/src/network/access/qnetworkaccesscache_p.h @@ -79,6 +79,7 @@ public: QByteArray key; bool expires; bool shareable; + qint64 expiryTimeoutSeconds; public: CacheableObject(); virtual ~CacheableObject(); @@ -95,7 +96,7 @@ public: void clear(); - void addEntry(const QByteArray &key, CacheableObject *entry); + void addEntry(const QByteArray &key, CacheableObject *entry, qint64 connectionCacheExpiryTimeoutSeconds = -1); bool hasEntry(const QByteArray &key) const; bool requestEntry(const QByteArray &key, QObject *target, const char *member); CacheableObject *requestEntryNow(const QByteArray &key); diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 69597b1ec8..4fc1f81c95 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -812,6 +812,9 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq // Propagate Http/2 settings: delegate->http2Parameters = request.http2Configuration(); + if (request.attribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute).isValid()) + delegate->connectionCacheExpiryTimeoutSeconds = request.attribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute).toInt(); + // For the synchronous HTTP, this is the normal way the delegate gets deleted // For the asynchronous HTTP this is a safety measure, the delegate deletes itself when HTTP is finished QMetaObject::Connection threadFinishedConnection = diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index f6a6f09670..688c29935c 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -319,6 +319,12 @@ QT_BEGIN_NAMESPACE the QNetworkReply after having emitted "finished". (This value was introduced in 5.14.) + \value ConnectionCacheExpiryTimeoutSecondsAttribute + Requests only, type: QMetaType::Int + To set when the TCP connections to a server (HTTP1 and HTTP2) should + be closed after the last pending request had been processed. + (This value was introduced in 6.3.) + \value User Special type. Additional information can be passed in QVariants with types ranging from User to UserMax. The default diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index 72848ff490..bdcb1c80a9 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -97,6 +97,7 @@ public: Http2DirectAttribute, ResourceTypeAttribute, // internal AutoDeleteReplyOnFinishAttribute, + ConnectionCacheExpiryTimeoutSecondsAttribute, User = 1000, UserMax = 32767 |