summaryrefslogtreecommitdiffstats
path: root/src/network/access
diff options
context:
space:
mode:
authorMarkus Goetz <markus@woboq.com>2021-06-14 17:40:06 +0200
committerMarkus Goetz <markus@woboq.com>2021-07-27 17:16:58 +0200
commit85cfbae1d62617fdc452680813f993e812bb55dd (patch)
tree647525ccc36d6b3ff707f206869f882ff104a4c3 /src/network/access
parent5597e26256f37168b1da2bf8b6c1a9ab7ab2618c (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/network/access')
-rw-r--r--src/network/access/qhttpthreaddelegate.cpp3
-rw-r--r--src/network/access/qhttpthreaddelegate_p.h1
-rw-r--r--src/network/access/qnetworkaccesscache.cpp59
-rw-r--r--src/network/access/qnetworkaccesscache_p.h3
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp3
-rw-r--r--src/network/access/qnetworkrequest.cpp6
-rw-r--r--src/network/access/qnetworkrequest.h1
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