Support for std::chrono as transferTimeout type

Provide users with means to use more modern time/duration type.

Please note that since QTimer does not currently support
timeouts larger than 'int' milliseconds, the limit on how long
durations can be expressed, remains. This should not
be an issue in practice with network requests, as a typical
int32 system can express timeouts of ~24 days.

[ChangeLog][QtNetwork][QNetworkAccessManager] Add std::chrono
support for transfer timeout.

[ChangeLog][QtNetwork][QNetworkRequest] Add std::chrono support
for transfer timeout.

Fixes: QTBUG-118714
Change-Id: If85678a5994c59bac5926e47f98c9cfeb2a07c30
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
This commit is contained in:
Juha Vuolle 2023-11-02 14:41:39 +02:00
parent 298d5a4bbd
commit 090991123d
7 changed files with 152 additions and 97 deletions

View File

@ -68,6 +68,7 @@
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
using namespace std::chrono_literals;
Q_LOGGING_CATEGORY(lcQnam, "qt.network.access.manager")
@ -1172,8 +1173,8 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
}
#if QT_CONFIG(http) || defined (Q_OS_WASM)
if (!req.transferTimeout())
req.setTransferTimeout(transferTimeout());
if (req.transferTimeoutAsDuration() == 0ms)
req.setTransferTimeout(transferTimeoutAsDuration());
#endif
if (autoDeleteReplies()
@ -1428,12 +1429,11 @@ void QNetworkAccessManager::setAutoDeleteReplies(bool shouldAutoDelete)
Returns the timeout used for transfers, in milliseconds.
This timeout is zero if setTransferTimeout() hasn't been
called, which means that the timeout is not used.
\sa setTransferTimeout()
*/
int QNetworkAccessManager::transferTimeout() const
{
return d_func()->transferTimeout;
return int(d_func()->transferTimeout.count());
}
/*!
@ -1441,21 +1441,49 @@ int QNetworkAccessManager::transferTimeout() const
Sets \a timeout as the transfer timeout in milliseconds.
\sa setTransferTimeout(std::chrono::milliseconds),
transferTimeout(), transferTimeoutAsDuration()
*/
void QNetworkAccessManager::setTransferTimeout(int timeout)
{
setTransferTimeout(std::chrono::milliseconds(timeout));
}
/*!
\since 6.7
Returns the timeout duration after which the transfer is aborted if no
data is exchanged.
The default duration is zero, which means that the timeout is not used.
\sa setTransferTimeout(std::chrono::milliseconds)
*/
std::chrono::milliseconds QNetworkAccessManager::transferTimeoutAsDuration() const
{
return d_func()->transferTimeout;
}
/*!
\since 6.7
Sets the timeout \a duration to abort the transfer if no data is exchanged.
Transfers are aborted if no bytes are transferred before
the timeout expires. Zero means no timer is set. If no
argument is provided, the timeout is
QNetworkRequest::DefaultTransferTimeoutConstant. If this function
QNetworkRequest::DefaultTransferTimeout. If this function
is not called, the timeout is disabled and has the
value zero. The request-specific non-zero timeouts set for
the requests that are executed override this value. This means
that if QNetworkAccessManager has an enabled timeout, it needs
to be disabled to execute a request without a timeout.
\sa transferTimeout()
*/
void QNetworkAccessManager::setTransferTimeout(int timeout)
\sa transferTimeoutAsDuration()
*/
void QNetworkAccessManager::setTransferTimeout(std::chrono::milliseconds duration)
{
d_func()->transferTimeout = timeout;
d_func()->transferTimeout = duration;
}
void QNetworkAccessManagerPrivate::_q_replyFinished(QNetworkReply *reply)

View File

@ -112,7 +112,11 @@ public:
void setAutoDeleteReplies(bool autoDelete);
int transferTimeout() const;
void setTransferTimeout(int timeout = QNetworkRequest::DefaultTransferTimeoutConstant);
void setTransferTimeout(int timeout);
std::chrono::milliseconds transferTimeoutAsDuration() const;
void setTransferTimeout(std::chrono::milliseconds duration =
QNetworkRequest::DefaultTransferTimeout);
Q_SIGNALS:
#ifndef QT_NO_NETWORKPROXY

View File

@ -131,7 +131,7 @@ public:
bool autoDeleteReplies = false;
int transferTimeout = 0;
std::chrono::milliseconds transferTimeout{0};
Q_DECLARE_PUBLIC(QNetworkAccessManager)
};

View File

@ -34,6 +34,7 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
using namespace QtMiscUtils;
using namespace std::chrono_literals;
class QNetworkProxy;
@ -2047,9 +2048,9 @@ void QNetworkReplyHttpImplPrivate::setupTransferTimeout()
Qt::QueuedConnection);
}
transferTimeout->stop();
if (request.transferTimeout()) {
if (request.transferTimeoutAsDuration() > 0ms) {
transferTimeout->setSingleShot(true);
transferTimeout->setInterval(request.transferTimeout());
transferTimeout->setInterval(request.transferTimeoutAsDuration());
QMetaObject::invokeMethod(transferTimeout, "start",
Qt::QueuedConnection);

View File

@ -28,6 +28,7 @@
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
using namespace std::chrono_literals;
QT_IMPL_METATYPE_EXTERN(QNetworkRequest)
QT_IMPL_METATYPE_EXTERN_TAGGED(QNetworkRequest::RedirectPolicy, QNetworkRequest__RedirectPolicy)
@ -407,6 +408,16 @@ QT_IMPL_METATYPE_EXTERN_TAGGED(QNetworkRequest::RedirectPolicy, QNetworkRequest_
\value DefaultTransferTimeoutConstant The transfer timeout in milliseconds.
Used if setTimeout() is called
without an argument.
\sa QNetworkRequest::DefaultTransferTimeout
*/
/*!
\variable QNetworkRequest::DefaultTransferTimeout
The transfer timeout with \l {QNetworkRequest::TransferTimeoutConstant}
milliseconds. Used if setTransferTimeout() is called without an
argument.
*/
class QNetworkRequestPrivate: public QSharedData, public QNetworkHeadersPrivate
@ -419,7 +430,6 @@ public:
, sslConfiguration(nullptr)
#endif
, maxRedirectsAllowed(maxRedirectCount)
, transferTimeout(0)
{ qRegisterMetaType<QNetworkRequest>(); }
~QNetworkRequestPrivate()
{
@ -479,7 +489,7 @@ public:
QHttp2Configuration h2Configuration;
qint64 decompressedSafetyCheckThreshold = 10ll * 1024ll * 1024ll;
#endif
int transferTimeout;
std::chrono::milliseconds transferTimeout = 0ms;
};
/*!
@ -977,14 +987,11 @@ void QNetworkRequest::setDecompressedSafetyCheckThreshold(qint64 threshold)
Returns the timeout used for transfers, in milliseconds.
This timeout is zero if setTransferTimeout hasn't been
called, which means that the timeout is not used.
\sa setTransferTimeout
\sa setTransferTimeout()
*/
int QNetworkRequest::transferTimeout() const
{
return d->transferTimeout;
return int(d->transferTimeout.count());
}
/*!
@ -992,18 +999,46 @@ int QNetworkRequest::transferTimeout() const
Sets \a timeout as the transfer timeout in milliseconds.
Transfers are aborted if no bytes are transferred before
the timeout expires. Zero means no timer is set. If no
argument is provided, the timeout is
QNetworkRequest::DefaultTransferTimeoutConstant. If this function
is not called, the timeout is disabled and has the
value zero.
\sa transferTimeout
\sa setTransferTimeout(std::chrono::milliseconds),
transferTimeout(), transferTimeoutAsDuration()
*/
void QNetworkRequest::setTransferTimeout(int timeout)
{
d->transferTimeout = timeout;
d->transferTimeout = std::chrono::milliseconds(timeout);
}
/*!
\since 6.7
Returns the timeout duration after which the transfer is aborted if no
data is exchanged.
The default duration is zero, which means that the timeout is not used.
\sa setTransferTimeout(std::chrono::milliseconds)
*/
std::chrono::milliseconds QNetworkRequest::transferTimeoutAsDuration() const
{
return d->transferTimeout;
}
/*!
\since 6.7
Sets the timeout \a duration to abort the transfer if no data is exchanged.
Transfers are aborted if no bytes are transferred before
the timeout expires. Zero means no timer is set. If no
argument is provided, the timeout is
QNetworkRequest::DefaultTransferTimeout. If this function
is not called, the timeout is disabled and has the
value zero.
\sa transferTimeoutAsDuration()
*/
void QNetworkRequest::setTransferTimeout(std::chrono::milliseconds duration)
{
d->transferTimeout = duration;
}
#endif // QT_CONFIG(http) || defined (Q_OS_WASM)

View File

@ -97,6 +97,9 @@ public:
DefaultTransferTimeoutConstant = 30000
};
static constexpr auto DefaultTransferTimeout =
std::chrono::milliseconds(DefaultTransferTimeoutConstant);
QNetworkRequest();
explicit QNetworkRequest(const QUrl &url);
QNetworkRequest(const QNetworkRequest &other);
@ -163,7 +166,9 @@ public:
#if QT_CONFIG(http) || defined (Q_OS_WASM)
int transferTimeout() const;
void setTransferTimeout(int timeout = DefaultTransferTimeoutConstant);
void setTransferTimeout(int timeout);
std::chrono::milliseconds transferTimeoutAsDuration() const;
void setTransferTimeout(std::chrono::milliseconds duration = DefaultTransferTimeout);
#endif // QT_CONFIG(http) || defined (Q_OS_WASM)
private:
QSharedDataPointer<QNetworkRequestPrivate> d;

View File

@ -91,6 +91,7 @@ Q_DECLARE_METATYPE(QNetworkProxyQuery)
typedef QSharedPointer<QNetworkReply> QNetworkReplyPtr;
using namespace Qt::StringLiterals;
using namespace std::chrono_literals;
#if QT_CONFIG(ssl)
QT_BEGIN_NAMESPACE
@ -535,8 +536,8 @@ private Q_SLOTS:
void autoDeleteReplies_data();
void autoDeleteReplies();
void getWithTimeout();
void postWithTimeout();
void requestWithTimeout_data();
void requestWithTimeout();
void moreActivitySignals_data();
void moreActivitySignals();
@ -9884,82 +9885,63 @@ void tst_QNetworkReply::autoDeleteReplies()
}
}
void tst_QNetworkReply::getWithTimeout()
void tst_QNetworkReply::requestWithTimeout_data()
{
MiniHttpServer server(tst_QNetworkReply::httpEmpty200Response, false);
using Operation = QNetworkAccessManager::Operation;
QTest::addColumn<Operation>("method");
QTest::addColumn<int>("reqInt");
QTest::addColumn<std::chrono::milliseconds>("reqChrono");
QTest::addColumn<int>("mgrInt");
QTest::addColumn<std::chrono::milliseconds>("mgrChrono");
QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort())));
QNetworkReplyPtr reply(manager.get(request));
QSignalSpy spy(reply.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError)));
QTest::addRow("get_req_int") << Operation::GetOperation << 500 << 0ms << 0 << 0ms;
QTest::addRow("get_req_chrono") << Operation::GetOperation << 0 << 500ms << 0 << 0ms;
QTest::addRow("get_mgr_int") << Operation::GetOperation << 0 << 0ms << 500 << 0ms;
QTest::addRow("get_mgr_chrono") << Operation::GetOperation << 0 << 0ms << 0 << 500ms;
QCOMPARE(waitForFinish(reply), int(Success));
QCOMPARE(spy.size(), 0);
QVERIFY(reply->error() == QNetworkReply::NoError);
request.setTransferTimeout(1000);
server.stopTransfer = true;
QNetworkReplyPtr reply2(manager.get(request));
QSignalSpy spy2(reply2.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError)));
QCOMPARE(waitForFinish(reply2), int(Failure));
QCOMPARE(spy2.size(), 1);
QVERIFY(reply2->error() == QNetworkReply::OperationCanceledError);
request.setTransferTimeout(0);
manager.setTransferTimeout(1000);
QNetworkReplyPtr reply3(manager.get(request));
QSignalSpy spy3(reply3.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError)));
QCOMPARE(waitForFinish(reply3), int(Failure));
QCOMPARE(spy3.size(), 1);
QVERIFY(reply3->error() == QNetworkReply::OperationCanceledError);
manager.setTransferTimeout(0);
QTest::addRow("post_req_int") << Operation::PostOperation << 500 << 0ms << 0 << 0ms;
QTest::addRow("post_req_chrono") << Operation::PostOperation << 0 << 500ms << 0 << 0ms;
QTest::addRow("post_mgr_int") << Operation::PostOperation << 0 << 0ms << 500 << 0ms;
QTest::addRow("post_mgr_chrono") << Operation::PostOperation << 0 << 0ms << 0 << 500ms;
}
void tst_QNetworkReply::postWithTimeout()
void tst_QNetworkReply::requestWithTimeout()
{
QFETCH(QNetworkAccessManager::Operation, method);
QFETCH(int, reqInt);
QFETCH(int, mgrInt);
QFETCH(std::chrono::milliseconds, reqChrono);
QFETCH(std::chrono::milliseconds, mgrChrono);
const auto data = "some data"_ba;
// Manager instance remains between case runs => always reset it's transferTimeout to
// ensure setting its transferTimeout in this case has effect
manager.setTransferTimeout(0ms);
MiniHttpServer server(tst_QNetworkReply::httpEmpty200Response, false);
server.stopTransfer = true;
QNetworkRequest request(QUrl("http://localhost:" + QString::number(server.serverPort())));
request.setRawHeader("Content-Type", "application/octet-stream");
QByteArray postData("Just some nonsense");
QNetworkReplyPtr reply(manager.post(request, postData));
QSignalSpy spy(reply.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError)));
if (reqInt > 0)
request.setTransferTimeout(reqInt);
if (reqChrono > 0ms)
request.setTransferTimeout(reqChrono);
if (mgrInt > 0)
manager.setTransferTimeout(mgrInt);
if (mgrChrono > 0ms)
manager.setTransferTimeout(mgrChrono);
QCOMPARE(waitForFinish(reply), int(Success));
QNetworkReplyPtr reply;
if (method == QNetworkAccessManager::GetOperation)
reply.reset(manager.get(request));
else if (method == QNetworkAccessManager::PostOperation)
reply.reset(manager.post(request, data));
QVERIFY(reply);
QCOMPARE(spy.size(), 0);
QVERIFY(reply->error() == QNetworkReply::NoError);
request.setTransferTimeout(1000);
server.stopTransfer = true;
QNetworkReplyPtr reply2(manager.post(request, postData));
QSignalSpy spy2(reply2.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError)));
QCOMPARE(waitForFinish(reply2), int(Failure));
QCOMPARE(spy2.size(), 1);
QVERIFY(reply2->error() == QNetworkReply::OperationCanceledError);
request.setTransferTimeout(0);
manager.setTransferTimeout(1000);
QNetworkReplyPtr reply3(manager.post(request, postData));
QSignalSpy spy3(reply3.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError)));
QCOMPARE(waitForFinish(reply3), int(Failure));
QCOMPARE(spy3.size(), 1);
QVERIFY(reply3->error() == QNetworkReply::OperationCanceledError);
manager.setTransferTimeout(0);
QSignalSpy spy(reply.data(), &QNetworkReply::errorOccurred);
QCOMPARE(waitForFinish(reply), int(Failure));
QCOMPARE(spy.size(), 1);
QCOMPARE(reply->error(), QNetworkReply::OperationCanceledError);
}
void tst_QNetworkReply::moreActivitySignals_data()