From 1b2ff0f6f8f93c4e3d3aff38359b1b2037a13378 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Wed, 30 Nov 2022 09:54:30 +0300 Subject: [PATCH] Handle tracker status updates asynchronously * Add a helper for performing jobs in Session context * Handle tracker status updates asynchronously PR #18010. --- src/base/bittorrent/session.h | 2 +- src/base/bittorrent/sessionimpl.cpp | 74 ++++++++++++++++++++----- src/base/bittorrent/sessionimpl.h | 13 ++++- src/base/bittorrent/torrentimpl.cpp | 49 +++++------------ src/base/bittorrent/torrentimpl.h | 9 +--- src/gui/transferlistfilterswidget.cpp | 77 ++++++++++++--------------- src/gui/transferlistfilterswidget.h | 6 ++- 7 files changed, 127 insertions(+), 103 deletions(-) diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index 981917dd4..464023f5f 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -468,6 +468,6 @@ namespace BitTorrent void trackersRemoved(Torrent *torrent, const QStringList &trackers); void trackerSuccess(Torrent *torrent, const QString &tracker); void trackerWarning(Torrent *torrent, const QString &tracker); - void trackerEntriesUpdated(const QHash> &updateInfos); + void trackerEntriesUpdated(Torrent *torrent, const QHash &updatedTrackerEntries); }; } diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index ac02c3397..32b53b92e 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015 Vladimir Golovnev + * Copyright (C) 2015-2022 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * This program is free software; you can redistribute it and/or @@ -35,7 +35,6 @@ #include #include #include -#include #ifdef Q_OS_WIN #include @@ -118,6 +117,24 @@ const Path CATEGORIES_FILE_NAME {u"categories.json"_qs}; const int MAX_PROCESSING_RESUMEDATA_COUNT = 50; const int STATISTICS_SAVE_INTERVAL = std::chrono::milliseconds(15min).count(); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) +namespace std +{ + uint qHash(const std::string &key, uint seed = 0) + { + return qHash(QByteArray::fromRawData(key.data(), static_cast(key.length())), seed); + } +} + +namespace libtorrent +{ + uint qHash(const libtorrent::torrent_handle &key) + { + return static_cast(libtorrent::hash_value(key)); + } +} +#endif + namespace { const char PEER_ID[] = "qB"; @@ -5809,13 +5826,12 @@ void SessionImpl::handleTrackerAlert(const lt::tracker_alert *a) if (!torrent) return; - const auto trackerURL = QString::fromUtf8(a->tracker_url()); - m_updatedTrackerEntries[torrent].insert(trackerURL); + QMap &updateInfo = m_updatedTrackerEntries[torrent->nativeHandle()][std::string(a->tracker_url())]; if (a->type() == lt::tracker_reply_alert::alert_type) { const int numPeers = static_cast(a)->num_peers; - torrent->updatePeerCount(trackerURL, a->local_endpoint, numPeers); + updateInfo.insert(a->local_endpoint, numPeers); } } @@ -5869,20 +5885,50 @@ void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert *a void SessionImpl::processTrackerStatuses() { + if (m_updatedTrackerEntries.isEmpty()) + return; + for (auto it = m_updatedTrackerEntries.cbegin(); it != m_updatedTrackerEntries.cend(); ++it) { - auto torrent = static_cast(it.key()); - const QSet &updatedTrackers = it.value(); + invokeAsync([this, torrentHandle = it.key(), updatedTrackers = it.value()]() mutable + { + try + { + std::vector nativeTrackers = torrentHandle.trackers(); + invoke([this, torrentHandle, nativeTrackers = std::move(nativeTrackers) + , updatedTrackers = std::move(updatedTrackers)] + { + TorrentImpl *torrent = m_torrents.value(torrentHandle.info_hash()); + if (!torrent) + return; - for (const QString &trackerURL : updatedTrackers) - torrent->invalidateTrackerEntry(trackerURL); - } + QHash updatedTrackerEntries; + updatedTrackerEntries.reserve(updatedTrackers.size()); + for (const lt::announce_entry &announceEntry : nativeTrackers) + { + const auto updatedTrackersIter = updatedTrackers.find(announceEntry.url); + if (updatedTrackersIter == updatedTrackers.end()) + continue; - if (!m_updatedTrackerEntries.isEmpty()) - { - emit trackerEntriesUpdated(m_updatedTrackerEntries); - m_updatedTrackerEntries.clear(); + const QMap &updateInfo = updatedTrackersIter.value(); + TrackerEntry trackerEntry = torrent->updateTrackerEntry(announceEntry, updateInfo); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + updatedTrackerEntries[trackerEntry.url] = std::move(trackerEntry); +#else + updatedTrackerEntries.emplace(trackerEntry.url, std::move(trackerEntry)); +#endif + } + + emit trackerEntriesUpdated(torrent, updatedTrackerEntries); + }); + } + catch (const std::exception &) + { + } + }); } + + m_updatedTrackerEntries.clear(); } void SessionImpl::saveStatistics() const diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index 0e3f792ab..ba306feb5 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -29,6 +29,7 @@ #pragma once +#include #include #include @@ -36,11 +37,11 @@ #include #include +#include #include #include #include #include -#include #include #include "base/path.h" @@ -438,6 +439,12 @@ namespace BitTorrent void addMappedPorts(const QSet &ports); void removeMappedPorts(const QSet &ports); + template + void invoke(Func &&func) + { + QMetaObject::invokeMethod(this, std::forward(func)); + } + void invokeAsync(std::function func); private slots: @@ -719,7 +726,9 @@ namespace BitTorrent QMap m_categories; QSet m_tags; - QHash> m_updatedTrackerEntries; + // This field holds amounts of peers reported by trackers in their responses to announces + // (torrent.tracker_name.tracker_local_endpoint.num_peers) + QHash>> m_updatedTrackerEntries; // I/O errored torrents QSet m_recentErroredTorrents; diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 31a098991..a94cd8d62 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -82,10 +82,10 @@ namespace #ifdef QBT_USES_LIBTORRENT2 void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry - , const lt::info_hash_t &hashes, const QMap &updateInfo) + , const lt::info_hash_t &hashes, const QMap &updateInfo) #else void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry - , const QMap &updateInfo) + , const QMap &updateInfo) #endif { Q_ASSERT(trackerEntry.url == QString::fromStdString(nativeEntry.url)); @@ -522,9 +522,6 @@ void TorrentImpl::setAutoManaged(const bool enable) QVector TorrentImpl::trackers() const { - if (!m_updatedTrackerEntries.isEmpty()) - refreshTrackerEntries(); - return m_trackerEntries; } @@ -1515,43 +1512,25 @@ void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileN endReceivedMetadataHandling(savePath, fileNames); } -void TorrentImpl::updatePeerCount(const QString &trackerURL, const TrackerEntry::Endpoint &endpoint, const int count) -{ - m_updatedTrackerEntries[trackerURL][endpoint] = count; -} - -void TorrentImpl::invalidateTrackerEntry(const QString &trackerURL) -{ - std::ignore = m_updatedTrackerEntries[trackerURL]; -} - -void TorrentImpl::refreshTrackerEntries() const +TrackerEntry TorrentImpl::updateTrackerEntry(const lt::announce_entry &announceEntry, const QMap &updateInfo) { - const std::vector nativeTrackers = m_nativeHandle.trackers(); - Q_ASSERT(nativeTrackers.size() == m_trackerEntries.size()); - - for (TrackerEntry &trackerEntry : m_trackerEntries) + const auto it = std::find_if(m_trackerEntries.begin(), m_trackerEntries.end() + , [&announceEntry](const TrackerEntry &trackerEntry) { - const auto updatedTrackerIter = m_updatedTrackerEntries.find(trackerEntry.url); - if (updatedTrackerIter == m_updatedTrackerEntries.end()) - continue; + return (trackerEntry.url == QString::fromStdString(announceEntry.url)); + }); - const auto nativeTrackerIter = std::find_if(nativeTrackers.cbegin(), nativeTrackers.cend() - , [trackerURL = trackerEntry.url.toStdString()](const lt::announce_entry &announceEntry) - { - return (announceEntry.url == trackerURL); - }); - Q_ASSERT(nativeTrackerIter != nativeTrackers.cend()); + Q_ASSERT(it != m_trackerEntries.end()); + // TODO: use [[unlikely]] in C++20 + if (Q_UNLIKELY(it == m_trackerEntries.end())) + return {}; - const lt::announce_entry &announceEntry = *nativeTrackerIter; #ifdef QBT_USES_LIBTORRENT2 - updateTrackerEntry(trackerEntry, announceEntry, m_nativeHandle.info_hashes(), updatedTrackerIter.value()); + ::updateTrackerEntry(*it, announceEntry, nativeHandle().info_hashes(), updateInfo); #else - updateTrackerEntry(trackerEntry, announceEntry, updatedTrackerIter.value()); + ::updateTrackerEntry(*it, announceEntry, updateInfo); #endif - } - - m_updatedTrackerEntries.clear(); + return *it; } std::shared_ptr TorrentImpl::nativeTorrentInfo() const diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index d8947ba1b..ee70a70a9 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -248,15 +248,13 @@ namespace BitTorrent void saveResumeData(lt::resume_data_flags_t flags = {}); void handleMoveStorageJobFinished(const Path &path, bool hasOutstandingJob); void fileSearchFinished(const Path &savePath, const PathList &fileNames); - void updatePeerCount(const QString &trackerURL, const TrackerEntry::Endpoint &endpoint, int count); - void invalidateTrackerEntry(const QString &trackerURL); + TrackerEntry updateTrackerEntry(const lt::announce_entry &announceEntry, const QMap &updateInfo); private: using EventTrigger = std::function; std::shared_ptr nativeTorrentInfo() const; - void refreshTrackerEntries() const; void updateStatus(const lt::torrent_status &nativeStatus); void updateState(); @@ -316,10 +314,7 @@ namespace BitTorrent MaintenanceJob m_maintenanceJob = MaintenanceJob::None; - // TODO: Use QHash once Qt5 is dropped. - using TrackerEntryUpdateInfo = QMap; - mutable QHash m_updatedTrackerEntries; - mutable QVector m_trackerEntries; + QVector m_trackerEntries; FileErrorInfo m_lastFileError; // Persistent data diff --git a/src/gui/transferlistfilterswidget.cpp b/src/gui/transferlistfilterswidget.cpp index 4a7c0281d..cdb2cb334 100644 --- a/src/gui/transferlistfilterswidget.cpp +++ b/src/gui/transferlistfilterswidget.cpp @@ -599,60 +599,52 @@ void TrackerFiltersList::setDownloadTrackerFavicon(bool value) } } -void TrackerFiltersList::handleTrackerEntriesUpdated(const QHash> &updateInfos) +void TrackerFiltersList::handleTrackerEntriesUpdated(const BitTorrent::Torrent *torrent + , const QHash &updatedTrackerEntries) { - for (auto torrentsIt = updateInfos.cbegin(); torrentsIt != updateInfos.cend(); ++torrentsIt) - { - const BitTorrent::Torrent *torrent = torrentsIt.key(); - const QSet &trackerURLs = torrentsIt.value(); - const BitTorrent::TorrentID id = torrent->id(); + const BitTorrent::TorrentID id = torrent->id(); - auto errorHashesIt = m_errors.find(id); - auto warningHashesIt = m_warnings.find(id); + auto errorHashesIt = m_errors.find(id); + auto warningHashesIt = m_warnings.find(id); - const QVector trackers = torrent->trackers(); - for (const BitTorrent::TrackerEntry &trackerEntry : trackers) + for (const BitTorrent::TrackerEntry &trackerEntry : updatedTrackerEntries) + { + if (trackerEntry.status == BitTorrent::TrackerEntry::Working) { - if (!trackerURLs.contains(trackerEntry.url)) - continue; - - if (trackerEntry.status == BitTorrent::TrackerEntry::Working) + if (errorHashesIt != m_errors.end()) { - if (errorHashesIt != m_errors.end()) - { - QSet &errored = errorHashesIt.value(); - errored.remove(trackerEntry.url); - } + QSet &errored = errorHashesIt.value(); + errored.remove(trackerEntry.url); + } - if (trackerEntry.message.isEmpty()) - { - if (warningHashesIt != m_warnings.end()) - { - QSet &warned = *warningHashesIt; - warned.remove(trackerEntry.url); - } - } - else + if (trackerEntry.message.isEmpty()) + { + if (warningHashesIt != m_warnings.end()) { - if (warningHashesIt == m_warnings.end()) - warningHashesIt = m_warnings.insert(id, {}); - warningHashesIt.value().insert(trackerEntry.url); + QSet &warned = *warningHashesIt; + warned.remove(trackerEntry.url); } } - else if (trackerEntry.status == BitTorrent::TrackerEntry::NotWorking) + else { - if (errorHashesIt == m_errors.end()) - errorHashesIt = m_errors.insert(id, {}); - errorHashesIt.value().insert(trackerEntry.url); + if (warningHashesIt == m_warnings.end()) + warningHashesIt = m_warnings.insert(id, {}); + warningHashesIt.value().insert(trackerEntry.url); } } - - if ((errorHashesIt != m_errors.end()) && errorHashesIt.value().isEmpty()) - m_errors.erase(errorHashesIt); - if ((warningHashesIt != m_warnings.end()) && warningHashesIt.value().isEmpty()) - m_warnings.erase(warningHashesIt); + else if (trackerEntry.status == BitTorrent::TrackerEntry::NotWorking) + { + if (errorHashesIt == m_errors.end()) + errorHashesIt = m_errors.insert(id, {}); + errorHashesIt.value().insert(trackerEntry.url); + } } + if ((errorHashesIt != m_errors.end()) && errorHashesIt.value().isEmpty()) + m_errors.erase(errorHashesIt); + if ((warningHashesIt != m_warnings.end()) && warningHashesIt.value().isEmpty()) + m_warnings.erase(warningHashesIt); + item(ERROR_ROW)->setText(tr("Error (%1)").arg(m_errors.size())); item(WARNING_ROW)->setText(tr("Warning (%1)").arg(m_warnings.size())); @@ -920,9 +912,10 @@ void TransferListFiltersWidget::changeTrackerless(const BitTorrent::Torrent *tor m_trackerFilters->changeTrackerless(torrent, trackerless); } -void TransferListFiltersWidget::trackerEntriesUpdated(const QHash> &updateInfos) +void TransferListFiltersWidget::trackerEntriesUpdated(const BitTorrent::Torrent *torrent + , const QHash &updatedTrackerEntries) { - m_trackerFilters->handleTrackerEntriesUpdated(updateInfos); + m_trackerFilters->handleTrackerEntriesUpdated(torrent, updatedTrackerEntries); } void TransferListFiltersWidget::onCategoryFilterStateChanged(bool enabled) diff --git a/src/gui/transferlistfilterswidget.h b/src/gui/transferlistfilterswidget.h index 82a97b373..e02cee7ea 100644 --- a/src/gui/transferlistfilterswidget.h +++ b/src/gui/transferlistfilterswidget.h @@ -131,7 +131,8 @@ public: void removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers); void refreshTrackers(const BitTorrent::Torrent *torrent); void changeTrackerless(const BitTorrent::Torrent *torrent, bool trackerless); - void handleTrackerEntriesUpdated(const QHash> &updateInfos); + void handleTrackerEntriesUpdated(const BitTorrent::Torrent *torrent + , const QHash &updatedTrackerEntries); void setDownloadTrackerFavicon(bool value); private slots: @@ -180,7 +181,8 @@ public slots: void removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers); void refreshTrackers(const BitTorrent::Torrent *torrent); void changeTrackerless(const BitTorrent::Torrent *torrent, bool trackerless); - void trackerEntriesUpdated(const QHash> &updateInfos); + void trackerEntriesUpdated(const BitTorrent::Torrent *torrent + , const QHash &updatedTrackerEntries); private slots: void onCategoryFilterStateChanged(bool enabled);