diff options
-rw-r--r-- | src/library/coverartcache.cpp | 8 | ||||
-rw-r--r-- | src/library/trackcollection.cpp | 49 | ||||
-rw-r--r-- | src/library/trackcollection.h | 16 | ||||
-rw-r--r-- | src/library/trackcollectionmanager.cpp | 23 | ||||
-rw-r--r-- | src/library/trackcollectionmanager.h | 4 | ||||
-rw-r--r-- | src/musicbrainz/tagfetcher.cpp | 23 | ||||
-rw-r--r-- | src/musicbrainz/web/musicbrainzrecordingstask.cpp | 8 | ||||
-rw-r--r-- | src/network/jsonwebtask.cpp | 10 | ||||
-rw-r--r-- | src/network/webtask.cpp | 14 | ||||
-rw-r--r-- | src/track/globaltrackcache.cpp | 46 | ||||
-rw-r--r-- | src/track/globaltrackcache.h | 35 | ||||
-rw-r--r-- | src/util/thread_affinity.h | 20 |
12 files changed, 174 insertions, 82 deletions
diff --git a/src/library/coverartcache.cpp b/src/library/coverartcache.cpp index 4e523dfd6c..bcf0093d3b 100644 --- a/src/library/coverartcache.cpp +++ b/src/library/coverartcache.cpp @@ -1,13 +1,14 @@ +#include "library/coverartcache.h" + #include <QFutureWatcher> #include <QPixmapCache> #include <QtConcurrentRun> #include <QtDebug> -#include "library/coverartcache.h" #include "library/coverartutils.h" #include "util/compatibility.h" #include "util/logger.h" - +#include "util/thread_affinity.h" namespace { @@ -242,8 +243,7 @@ void CoverArtCache::coverLoaded() { res.coverArt.loadedImage.image = placeholderImage; } // Create pixmap, GUI thread only! - DEBUG_ASSERT(QThread::currentThread() == - QCoreApplication::instance()->thread()); + DEBUG_ASSERT_MAIN_THREAD_AFFINITY(); DEBUG_ASSERT(!res.coverArt.loadedImage.image.isNull()); pixmap = QPixmap::fromImage(res.coverArt.loadedImage.image); // Don't cache full size covers (resizedToWidth = 0) diff --git a/src/library/trackcollection.cpp b/src/library/trackcollection.cpp index 415dcb3c3d..82d2a25f95 100644 --- a/src/library/trackcollection.cpp +++ b/src/library/trackcollection.cpp @@ -1,5 +1,4 @@ #include <QApplication> -#include <QThread> #include "library/trackcollection.h" @@ -65,14 +64,14 @@ TrackCollection::~TrackCollection() { } void TrackCollection::repairDatabase(QSqlDatabase database) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); kLogger.info() << "Repairing database"; m_crates.repairDatabase(database); } void TrackCollection::connectDatabase(QSqlDatabase database) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); kLogger.info() << "Connecting database"; m_database = database; @@ -86,7 +85,7 @@ void TrackCollection::connectDatabase(QSqlDatabase database) { } void TrackCollection::disconnectDatabase() { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); kLogger.info() << "Disconnecting database"; m_database = QSqlDatabase(); @@ -95,7 +94,7 @@ void TrackCollection::disconnectDatabase() { } void TrackCollection::connectTrackSource(QSharedPointer<BaseTrackCache> pTrackSource) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); VERIFY_OR_DEBUG_ASSERT(m_pTrackSource.isNull()) { kLogger.warning() << "Track source has already been connected"; @@ -130,7 +129,7 @@ void TrackCollection::connectTrackSource(QSharedPointer<BaseTrackCache> pTrackSo } QWeakPointer<BaseTrackCache> TrackCollection::disconnectTrackSource() { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); auto pWeakPtr = m_pTrackSource.toWeakRef(); if (m_pTrackSource) { @@ -142,6 +141,8 @@ QWeakPointer<BaseTrackCache> TrackCollection::disconnectTrackSource() { } bool TrackCollection::addDirectory(const QString& dir) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + SqlTransaction transaction(m_database); switch (m_directoryDao.addDirectory(dir)) { case SQL_ERROR: @@ -158,6 +159,8 @@ bool TrackCollection::addDirectory(const QString& dir) { } bool TrackCollection::removeDirectory(const QString& dir) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + SqlTransaction transaction(m_database); switch (m_directoryDao.removeDirectory(dir)) { case SQL_ERROR: @@ -172,7 +175,7 @@ bool TrackCollection::removeDirectory(const QString& dir) { } void TrackCollection::relocateDirectory(QString oldDir, QString newDir) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); // We only call this method if the user has picked a relocated directory via // a file dialog. This means the system sandboxer (if we are sandboxed) has @@ -236,7 +239,7 @@ QList<TrackId> TrackCollection::resolveTrackIdsFromLocations( } bool TrackCollection::hideTracks(const QList<TrackId>& trackIds) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); // Warn if tracks have a playlist membership QSet<int> allPlaylistIds; @@ -302,11 +305,13 @@ bool TrackCollection::hideTracks(const QList<TrackId>& trackIds) { } void TrackCollection::hideAllTracks(const QDir& rootDir) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + m_trackDao.hideAllTracks(rootDir); } bool TrackCollection::unhideTracks(const QList<TrackId>& trackIds) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); VERIFY_OR_DEBUG_ASSERT(m_trackDao.unhideTracks(trackIds)) { return false; @@ -330,7 +335,7 @@ bool TrackCollection::unhideTracks(const QList<TrackId>& trackIds) { bool TrackCollection::purgeTracks( const QList<TrackId>& trackIds) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); // Transactional SqlTransaction transaction(m_database); @@ -369,6 +374,8 @@ bool TrackCollection::purgeTracks( bool TrackCollection::purgeAllTracks( const QDir& rootDir) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + QList<TrackRef> trackRefs = m_trackDao.getAllTrackRefs(rootDir); QList<TrackId> trackIds; trackIds.reserve(trackRefs.size()); @@ -382,7 +389,7 @@ bool TrackCollection::purgeAllTracks( bool TrackCollection::insertCrate( const Crate& crate, CrateId* pCrateId) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); // Transactional SqlTransaction transaction(m_database); @@ -409,7 +416,7 @@ bool TrackCollection::insertCrate( bool TrackCollection::updateCrate( const Crate& crate) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); // Transactional SqlTransaction transaction(m_database); @@ -431,7 +438,7 @@ bool TrackCollection::updateCrate( bool TrackCollection::deleteCrate( CrateId crateId) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); // Transactional SqlTransaction transaction(m_database); @@ -454,7 +461,7 @@ bool TrackCollection::deleteCrate( bool TrackCollection::addCrateTracks( CrateId crateId, const QList<TrackId>& trackIds) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); // Transactional SqlTransaction transaction(m_database); @@ -477,7 +484,7 @@ bool TrackCollection::addCrateTracks( bool TrackCollection::removeCrateTracks( CrateId crateId, const QList<TrackId>& trackIds) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); // Transactional SqlTransaction transaction(m_database); @@ -500,7 +507,7 @@ bool TrackCollection::removeCrateTracks( bool TrackCollection::updateAutoDjCrate( CrateId crateId, bool isAutoDjSource) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); Crate crate; VERIFY_OR_DEBUG_ASSERT(crates().readCrateById(crateId, &crate)) { @@ -514,18 +521,22 @@ bool TrackCollection::updateAutoDjCrate( } void TrackCollection::saveTrack(Track* pTrack) { - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); m_trackDao.saveTrack(pTrack); } TrackPointer TrackCollection::getTrackById( TrackId trackId) const { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + return m_trackDao.getTrackById(trackId); } TrackPointer TrackCollection::getTrackByRef( const TrackRef& trackRef) const { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + return m_trackDao.getTrackByRef(trackRef); } @@ -537,12 +548,16 @@ TrackId TrackCollection::getTrackIdByRef( TrackPointer TrackCollection::getOrAddTrack( const TrackRef& trackRef, bool* pAlreadyInLibrary) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + return m_trackDao.getOrAddTrack(trackRef, pAlreadyInLibrary); } TrackId TrackCollection::addTrack( const TrackPointer& pTrack, bool unremove) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + m_trackDao.addTracksPrepare(); const auto trackId = m_trackDao.addTracksAddTrack(pTrack, unremove); m_trackDao.addTracksFinish(!trackId.isValid()); diff --git a/src/library/trackcollection.h b/src/library/trackcollection.h index 9195e2660d..4e8af126ac 100644 --- a/src/library/trackcollection.h +++ b/src/library/trackcollection.h @@ -5,14 +5,15 @@ #include <QSharedPointer> #include <QSqlDatabase> -#include "preferences/usersettings.h" #include "library/crate/cratestorage.h" -#include "library/dao/trackdao.h" -#include "library/dao/cuedao.h" -#include "library/dao/playlistdao.h" #include "library/dao/analysisdao.h" +#include "library/dao/cuedao.h" #include "library/dao/directorydao.h" #include "library/dao/libraryhashdao.h" +#include "library/dao/playlistdao.h" +#include "library/dao/trackdao.h" +#include "preferences/usersettings.h" +#include "util/thread_affinity.h" // forward declaration(s) class BaseTrackCache; @@ -36,23 +37,29 @@ class TrackCollection : public QObject, void disconnectDatabase() override; QSqlDatabase database() const { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); return m_database; } const CrateStorage& crates() const { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); return m_crates; } TrackDAO& getTrackDAO() { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); return m_trackDao; } PlaylistDAO& getPlaylistDAO() { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); return m_playlistDao; } DirectoryDAO& getDirectoryDAO() { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); return m_directoryDao; } AnalysisDao& getAnalysisDAO() { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); return m_analysisDao; } @@ -60,6 +67,7 @@ class TrackCollection : public QObject, QWeakPointer<BaseTrackCache> disconnectTrackSource(); QSharedPointer<BaseTrackCache> getTrackSource() const { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); return m_pTrackSource; } diff --git a/src/library/trackcollectionmanager.cpp b/src/library/trackcollectionmanager.cpp index e912e7c3e3..a2c348717c 100644 --- a/src/library/trackcollectionmanager.cpp +++ b/src/library/trackcollectionmanager.cpp @@ -183,6 +183,7 @@ void TrackCollectionManager::saveEvictedTrack(Track* pTrack) noexcept { void TrackCollectionManager::saveTrack( Track* pTrack, TrackMetadataExportMode mode) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(pTrack); DEBUG_ASSERT(pTrack->getDateAdded().isValid()); @@ -274,14 +275,20 @@ void TrackCollectionManager::exportTrackMetadata( } bool TrackCollectionManager::addDirectory(const QString& dir) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + return m_pInternalCollection->addDirectory(dir); } bool TrackCollectionManager::removeDirectory(const QString& dir) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + return m_pInternalCollection->removeDirectory(dir); } void TrackCollectionManager::relocateDirectory(QString oldDir, QString newDir) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + kLogger.debug() << "Relocating directory in internal track collection:" << oldDir @@ -305,18 +312,26 @@ void TrackCollectionManager::relocateDirectory(QString oldDir, QString newDir) { } bool TrackCollectionManager::hideTracks(const QList<TrackId>& trackIds) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + return m_pInternalCollection->hideTracks(trackIds); } bool TrackCollectionManager::unhideTracks(const QList<TrackId>& trackIds) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + return m_pInternalCollection->unhideTracks(trackIds); } void TrackCollectionManager::hideAllTracks(const QDir& rootDir) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + m_pInternalCollection->hideAllTracks(rootDir); } void TrackCollectionManager::purgeTracks(const QList<TrackRef>& trackRefs) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + if (trackRefs.isEmpty()) { return; } @@ -358,6 +373,8 @@ void TrackCollectionManager::purgeTracks(const QList<TrackRef>& trackRefs) { } void TrackCollectionManager::purgeAllTracks(const QDir& rootDir) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + kLogger.debug() << "Purging directory" << rootDir @@ -384,6 +401,8 @@ void TrackCollectionManager::purgeAllTracks(const QDir& rootDir) { TrackPointer TrackCollectionManager::getOrAddTrack( const TrackRef& trackRef, bool* pAlreadyInLibrary) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + bool alreadyInLibrary; if (pAlreadyInLibrary) { alreadyInLibrary = *pAlreadyInLibrary; @@ -420,6 +439,8 @@ void TrackCollectionManager::slotScanTrackAdded(TrackPointer pTrack) { } void TrackCollectionManager::slotScanTracksUpdated(QSet<TrackId> updatedTrackIds) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + // Already updated in m_pInternalCollection if (updatedTrackIds.isEmpty()) { return; @@ -460,6 +481,8 @@ void TrackCollectionManager::slotScanTracksUpdated(QSet<TrackId> updatedTrackIds void TrackCollectionManager::slotScanTracksRelocated( QList<RelocatedTrack> relocatedTracks) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); + // Already replaced in m_pInternalCollection if (m_externalCollections.isEmpty()) { return; diff --git a/src/library/trackcollectionmanager.h b/src/library/trackcollectionmanager.h index eeda99ac73..751df279a5 100644 --- a/src/library/trackcollectionmanager.h +++ b/src/library/trackcollectionmanager.h @@ -3,7 +3,6 @@ #include <QDir> #include <QList> #include <QSet> - #include <memory> #include "library/relocatedtrack.h" @@ -11,6 +10,7 @@ #include "track/globaltrackcache.h" #include "util/db/dbconnectionpool.h" #include "util/parented_ptr.h" +#include "util/thread_affinity.h" class LibraryScanner; class TrackCollection; @@ -37,10 +37,12 @@ class TrackCollectionManager: public QObject, ~TrackCollectionManager() override; TrackCollection* internalCollection() { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); return m_pInternalCollection; } const QList<ExternalTrackCollection*>& externalCollections() const { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); return m_externalCollections; } diff --git a/src/musicbrainz/tagfetcher.cpp b/src/musicbrainz/tagfetcher.cpp index f447930ffe..1bf11d9546 100644 --- a/src/musicbrainz/tagfetcher.cpp +++ b/src/musicbrainz/tagfetcher.cpp @@ -4,6 +4,7 @@ #include <QtConcurrentRun> #include "musicbrainz/chromaprinter.h" +#include "util/thread_affinity.h" namespace { @@ -20,7 +21,7 @@ TagFetcher::TagFetcher(QObject* parent) void TagFetcher::startFetch( TrackPointer pTrack) { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); cancel(); m_pTrack = pTrack; @@ -39,7 +40,7 @@ void TagFetcher::startFetch( } void TagFetcher::cancel() { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); m_pTrack.reset(); m_fingerprintWatcher.disconnect(this); m_fingerprintWatcher.cancel(); @@ -56,7 +57,7 @@ void TagFetcher::cancel() { } void TagFetcher::slotFingerprintReady() { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); if (!m_pTrack || !m_fingerprintWatcher.isFinished()) { return; @@ -100,7 +101,7 @@ void TagFetcher::slotFingerprintReady() { void TagFetcher::slotAcoustIdTaskSucceeded( QList<QUuid> recordingIds) { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(m_pAcoustIdTask.get() == qobject_cast<mixxx::AcoustIdLookupTask*>(sender())); @@ -141,7 +142,7 @@ void TagFetcher::slotAcoustIdTaskSucceeded( void TagFetcher::slotAcoustIdTaskFailed( mixxx::network::JsonWebResponse response) { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(m_pAcoustIdTask.get() == qobject_cast<mixxx::AcoustIdLookupTask*>(sender())); @@ -155,7 +156,7 @@ void TagFetcher::slotAcoustIdTaskFailed( } void TagFetcher::slotAcoustIdTaskAborted() { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(m_pAcoustIdTask.get() == qobject_cast<mixxx::AcoustIdLookupTask*>(sender())); @@ -174,7 +175,7 @@ void TagFetcher::slotAcoustIdTaskNetworkError( QByteArray errorContent) { Q_UNUSED(requestUrl); Q_UNUSED(errorContent); - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(m_pAcoustIdTask.get() == qobject_cast<mixxx::AcoustIdLookupTask*>(sender())); @@ -188,7 +189,7 @@ void TagFetcher::slotAcoustIdTaskNetworkError( } void TagFetcher::slotMusicBrainzTaskAborted() { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(m_pMusicBrainzTask.get() == qobject_cast<mixxx::MusicBrainzRecordingsTask*>(sender())); @@ -207,7 +208,7 @@ void TagFetcher::slotMusicBrainzTaskNetworkError( QByteArray errorContent) { Q_UNUSED(requestUrl); Q_UNUSED(errorContent); - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(m_pMusicBrainzTask.get() == qobject_cast<mixxx::MusicBrainzRecordingsTask*>(sender())); @@ -224,7 +225,7 @@ void TagFetcher::slotMusicBrainzTaskFailed( mixxx::network::WebResponse response, int errorCode, QString errorMessage) { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(m_pMusicBrainzTask.get() == qobject_cast<mixxx::MusicBrainzRecordingsTask*>(sender())); @@ -239,7 +240,7 @@ void TagFetcher::slotMusicBrainzTaskFailed( void TagFetcher::slotMusicBrainzTaskSucceeded( QList<mixxx::musicbrainz::TrackRelease> guessedTrackReleases) { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(m_pMusicBrainzTask.get() == qobject_cast<mixxx::MusicBrainzRecordingsTask*>(sender())); diff --git a/src/musicbrainz/web/musicbrainzrecordingstask.cpp b/src/musicbrainz/web/musicbrainzrecordingstask.cpp index bdce39d78d..d3d6b0efd1 100644 --- a/src/musicbrainz/web/musicbrainzrecordingstask.cpp +++ b/src/musicbrainz/web/musicbrainzrecordingstask.cpp @@ -1,7 +1,6 @@ #include "musicbrainz/web/musicbrainzrecordingstask.h" #include <QMetaMethod> -#include <QThread> #include <QXmlStreamReader> #include "defs_urls.h" @@ -11,6 +10,7 @@ #include "util/assert.h" #include "util/compatibility.h" #include "util/logger.h" +#include "util/thread_affinity.h" #include "util/version.h" namespace mixxx { @@ -85,7 +85,7 @@ bool MusicBrainzRecordingsTask::doStart( QNetworkAccessManager* networkAccessManager, int parentTimeoutMillis) { m_parentTimeoutMillis = parentTimeoutMillis; - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(networkAccessManager); VERIFY_OR_DEBUG_ASSERT(!m_pendingNetworkReply) { kLogger.warning() @@ -150,7 +150,7 @@ QUrl MusicBrainzRecordingsTask::doAbort() { } QUrl MusicBrainzRecordingsTask::doTimeOut() { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); QUrl requestUrl; if (m_pendingNetworkReply) { requestUrl = timeOutPendingNetworkReply(m_pendingNetworkReply); @@ -162,7 +162,7 @@ QUrl MusicBrainzRecordingsTask::doTimeOut() { } void MusicBrainzRecordingsTask::slotNetworkReplyFinished() { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); const QPair<QNetworkReply*, network::HttpStatusCode> networkReplyWithStatusCode = receiveNetworkReply(); auto* const networkReply = networkReplyWithStatusCode.first; diff --git a/src/network/jsonwebtask.cpp b/src/network/jsonwebtask.cpp index dec24f602c..7a843145a3 100644 --- a/src/network/jsonwebtask.cpp +++ b/src/network/jsonwebtask.cpp @@ -6,7 +6,6 @@ #include <QMetaMethod> #include <QMimeDatabase> #include <QNetworkRequest> -#include <QThread> #include <QTimerEvent> #include <mutex> // std::once_flag @@ -16,6 +15,7 @@ #endif #include "util/counter.h" #include "util/logger.h" +#include "util/thread_affinity.h" namespace mixxx { @@ -240,7 +240,7 @@ bool JsonWebTask::doStart( QNetworkAccessManager* networkAccessManager, int parentTimeoutMillis) { Q_UNUSED(parentTimeoutMillis); - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(networkAccessManager); VERIFY_OR_DEBUG_ASSERT(!m_pendingNetworkReply) { kLogger.warning() @@ -287,7 +287,7 @@ bool JsonWebTask::doStart( } QUrl JsonWebTask::doAbort() { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); QUrl requestUrl; if (m_pendingNetworkReply) { requestUrl = abortPendingNetworkReply(m_pendingNetworkReply); @@ -301,7 +301,7 @@ QUrl JsonWebTask::doAbort() { } QUrl JsonWebTask::doTimeOut() { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); QUrl requestUrl; if (m_pendingNetworkReply) { requestUrl = timeOutPendingNetworkReply(m_pendingNetworkReply); @@ -313,7 +313,7 @@ QUrl JsonWebTask::doTimeOut() { } void JsonWebTask::slotNetworkReplyFinished() { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); const QPair<QNetworkReply*, HttpStatusCode> networkReplyWithStatusCode = receiveNetworkReply(); auto* const networkReply = networkReplyWithStatusCode.first; diff --git a/src/network/webtask.cpp b/src/network/webtask.cpp index 1b0950b642..49cc3fc819 100644 --- a/src/network/webtask.cpp +++ b/src/network/webtask.cpp @@ -1,12 +1,12 @@ #include "network/webtask.h" -#include <QThread> #include <QTimerEvent> #include <mutex> // std::once_flag #include "util/assert.h" #include "util/counter.h" #include "util/logger.h" +#include "util/thread_affinity.h" namespace mixxx { @@ -166,7 +166,7 @@ void WebTask::invokeAbort() { } void WebTask::slotStart(int timeoutMillis) { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(m_status != Status::Pending); VERIFY_OR_DEBUG_ASSERT(m_networkAccessManager) { onNetworkError( @@ -207,7 +207,7 @@ void WebTask::slotStart(int timeoutMillis) { QUrl WebTask::abortPendingNetworkReply( QNetworkReply* pendingNetworkReply) { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(pendingNetworkReply); if (pendingNetworkReply->isRunning()) { pendingNetworkReply->abort(); @@ -219,7 +219,7 @@ QUrl WebTask::abortPendingNetworkReply( QUrl WebTask::timeOutPendingNetworkReply( QNetworkReply* pendingNetworkReply) { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(pendingNetworkReply); if (pendingNetworkReply->isRunning()) { //pendingNetworkReply->abort(); @@ -230,7 +230,7 @@ QUrl WebTask::timeOutPendingNetworkReply( } QUrl WebTask::abort() { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); if (m_status != Status::Pending) { DEBUG_ASSERT(m_timeoutTimerId == kInvalidTimerId); return QUrl(); @@ -252,7 +252,7 @@ void WebTask::slotAbort() { } void WebTask::timerEvent(QTimerEvent* event) { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); const auto timerId = event->timerId(); DEBUG_ASSERT(timerId != kInvalidTimerId); if (timerId != m_timeoutTimerId) { @@ -270,7 +270,7 @@ void WebTask::timerEvent(QTimerEvent* event) { } QPair<QNetworkReply*, HttpStatusCode> WebTask::receiveNetworkReply() { - DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(m_status != Status::Idle); auto* const networkReply = qobject_cast<QNetworkReply*>(sender()); HttpStatusCode statusCode = kHttpStatusCodeInvalid; diff --git a/src/track/globaltrackcache.cpp b/src/track/globaltrackcache.cpp index 257c58af5b..e15df7026b 100644 --- a/src/track/globaltrackcache.cpp +++ b/src/track/globaltrackcache.cpp @@ -1,11 +1,10 @@ #include "track/globaltrackcache.h" -#include <QApplication> -#include <QThread> +#include <QCoreApplication> #include "util/assert.h" #include "util/logger.h" - +#include "util/thread_affinity.h" namespace { @@ -209,7 +208,8 @@ void GlobalTrackCache::createInstance( //static void GlobalTrackCache::destroyInstance() { - DEBUG_ASSERT(s_pInstance); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(s_pInstance); + kLogger.info() << "Destroying instance"; // Processing all pending events is required to evict all // remaining references from the cache. @@ -220,7 +220,6 @@ void GlobalTrackCache::destroyInstance() { // Reset the static/global pointer before entering the destructor s_pInstance = nullptr; // Delete the singular instance - DEBUG_ASSERT(QThread::currentThread() == pInstance->thread()); pInstance->deleteLater(); } @@ -261,20 +260,21 @@ void GlobalTrackCache::evictAndSaveCachedTrack(GlobalTrackCacheEntryPointer cach QMetaObject::invokeMethod( s_pInstance, #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - "evictAndSave" + "slotEvictAndSave", #else [cacheEntryPtr = std::move(cacheEntryPtr)] { - s_pInstance->evictAndSave(cacheEntryPtr); - } + s_pInstance->slotEvictAndSave(cacheEntryPtr); + }, #endif // Qt will choose either a direct or a queued connection // depending on the thread from which this method has // been invoked! - , Qt::AutoConnection + Qt::AutoConnection #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - , Q_ARG(GlobalTrackCacheEntryPointer, std::move(cacheEntryPtr)) + , + Q_ARG(GlobalTrackCacheEntryPointer, std::move(cacheEntryPtr)) #endif - ); + ); } else { // After the singular instance has been destroyed we are // not able to save pending changes. The track is deleted @@ -371,7 +371,7 @@ void GlobalTrackCache::saveEvictedTrack(Track* pEvictedTrack) const { } void GlobalTrackCache::deactivate() { - DEBUG_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); if (isEmpty()) { return; @@ -625,7 +625,7 @@ void GlobalTrackCache::resolve( // function might be called from any thread, even from worker // threads without an event loop. We need to move the newly // created object to the main thread. - savingPtr->moveToThread(QApplication::instance()->thread()); + savingPtr->moveToThread(QCoreApplication::instance()->thread()); pCacheResolver->initLookupResult( GlobalTrackCacheLookupResult::MISS, @@ -670,16 +670,14 @@ void GlobalTrackCache::purgeTrackId(TrackId trackId) { } } -void GlobalTrackCache::evictAndSave( +void GlobalTrackCache::slotEvictAndSave( GlobalTrackCacheEntryPointer cacheEntryPtr) { + DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); DEBUG_ASSERT(cacheEntryPtr); - // We need to be sure this is always called from the main thread - // because we can only access the DB from it and we must not lose the - // the lock until all changes are persistently stored in file and DB - // to not hand out the track again with old metadata. - DEBUG_ASSERT(QApplication::instance()->thread() == QThread::currentThread()); - + // GlobalTrackCacheSaver::saveEvictedTrack() requires that + // exclusive access is guaranteed for the duration of the + // whole invocation! GlobalTrackCacheLocker cacheLocker; if (!cacheEntryPtr->expired()) { @@ -708,8 +706,12 @@ void GlobalTrackCache::evictAndSave( DEBUG_ASSERT(!isCached(cacheEntryPtr->getPlainPtr())); saveEvictedTrack(cacheEntryPtr->getPlainPtr()); - // here the cacheEntryPtr goes out of scope, the cache entry is - // deleted including the owned track + // Explicitly release the cacheEntryPtr including the owned + // track object while the cache is still locked. + cacheEntryPtr.reset(); + + // Finally the exclusive lock on the cache is released implicitly + // when exiting the scope of this method. } bool GlobalTrackCache::tryEvict(Track* plainPtr) { diff --git a/src/track/globaltrackcache.h b/src/track/globaltrackcache.h index 2cfa85dac6..7c3c170254 100644 --- a/src/track/globaltrackcache.h +++ b/src/track/globaltrackcache.h @@ -174,19 +174,40 @@ private: TrackRef m_trackRef; }; +/// Callback interface for pre-delete actions class /*interface*/ GlobalTrackCacheSaver { private: friend class GlobalTrackCache; - virtual void saveEvictedTrack(Track* pEvictedTrack) noexcept = 0; -protected: - virtual ~GlobalTrackCacheSaver() = default; + /// Perform actions that are necessary to save any pending + /// modifications of a Track object before it finally gets + /// deleted. + /// + /// GlobalTrackCache ensures that the given pointer is valid + /// and the last and only reference to this Track object. + /// While invoked the GlobalTrackCache is locked to ensure + /// that this particular track is not accessible while + /// saving the Track object, e.g. by updating the database + /// and exporting file tags. + /// + /// This callback method will always be invoked from the + /// event loop thread of the owning GlobalTrackCache instance. + /// Typically the GlobalTrackCache lives on the main thread + /// that also controls access to the database. + /// NOTE(2020-06-06): If these assumptions about thread affinity + /// are no longer valid the design decisions need to be revisited + /// carefully! + virtual void saveEvictedTrack( + Track* pEvictedTrack) noexcept = 0; + + protected: + virtual ~GlobalTrackCacheSaver() = default; }; class GlobalTrackCache : public QObject { Q_OBJECT -public: + public: static void createInstance( GlobalTrackCacheSaver* pSaver, // A custom deleter is only needed for tests without an event loop! @@ -202,10 +223,10 @@ public: // Deleter callbacks for the smart-pointer static void evictAndSaveCachedTrack(GlobalTrackCacheEntryPointer cacheEntryPtr); -private slots: - void evictAndSave(GlobalTrackCacheEntryPointer cacheEntryPtr); + private slots: + void slotEvictAndSave(GlobalTrackCacheEntryPointer cacheEntryPtr); -private: + private: friend class GlobalTrackCacheLocker; friend class GlobalTrackCacheResolver; |