-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TrackCache: Fixes and optimizations (lp1738253/17382271738028) #1415
Changes from all commits
c28a86b
8d8b0ab
3c7f5a7
67b6b75
01fe7ca
bc407e6
8670584
f1611c6
b105664
afddf31
b6b7cbd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -154,17 +154,16 @@ void BrowseThread::populateModel() { | |
|
||
const QString filepath = fileIt.next(); | ||
{ | ||
TrackCacheLocker cacheLocker( | ||
TrackCache::instance().lookupOrCreateTemporaryForFile( | ||
filepath, thisPath.token())); | ||
auto pTrack = cacheLocker.getTrack(); | ||
if (!pTrack) { | ||
qWarning() << "Skipping inaccessible file" | ||
<< filepath; | ||
continue; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Original we skip inaccessible files here. Do you have an idea, when it happens? |
||
} | ||
|
||
// Import metadata from file | ||
TrackPointer pTrack = | ||
TrackCache::instance().resolve( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BaseTrackCache uses TrackCache this sounds like swapped class named from legacy. |
||
filepath, thisPath.token()).getTrack(); | ||
// The TrackCache is unlocked instantly even if a new track object | ||
// has been created and inserted into the cache. Newly created track | ||
// objects will only contain a reference of the corresponding file, | ||
// but not any metadata, yet. This reduces lock contention on the | ||
// global track cache. | ||
|
||
// Update the track object by (re-)importing metadata from the file | ||
SoundSourceProxy(pTrack).updateTrackFromSource(); | ||
|
||
item = new QStandardItem(pTrack->getFileName()); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -188,10 +188,12 @@ bool TrackDAO::trackExistsInDatabase(const QString& absoluteFilePath) { | |
return getTrackId(absoluteFilePath).isValid(); | ||
} | ||
|
||
void TrackDAO::saveTrack(Track* pTrack) { | ||
DEBUG_ASSERT(nullptr != pTrack); | ||
void TrackDAO::saveTrack(TrackCacheLocker* pCacheLocker, Track* pTrack) { | ||
DEBUG_ASSERT(pTrack); | ||
|
||
if (pTrack->isDirty()) { | ||
qDebug() << "TrackDAO: Saving track" << pTrack->getLocation(); | ||
|
||
// Write audio meta data, if enabled in the preferences. | ||
// | ||
// This must be done before updating the database, because | ||
|
@@ -202,6 +204,18 @@ void TrackDAO::saveTrack(Track* pTrack) { | |
SoundSourceProxy::exportTrackMetadataBeforeSaving(pTrack); | ||
} | ||
|
||
// The track cache can safely be unlocked now that the metadata has | ||
// been exported to the file. Updating the database is thread-safe | ||
// an we accept this very small chance of a race condition here. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A race condition between what? What can happen at worse? |
||
// Exporting metadata to a file cannot be rolled back if updating | ||
// the database fails, so we have to account for inconsistencies | ||
// in any case! | ||
// Unlocking the cache now reduces lock contention and keeps the | ||
// UI as responsive as possible. | ||
if (pCacheLocker) { | ||
pCacheLocker->unlockCache(); | ||
} | ||
|
||
// Only update the database if the track has already been added! | ||
const TrackId trackId(pTrack->getId()); | ||
if (trackId.isValid() && updateTrack(pTrack)) { | ||
|
@@ -635,15 +649,24 @@ TrackPointer TrackDAO::addTracksAddFile(const QFileInfo& fileInfo, bool unremove | |
|
||
TrackCacheResolver cacheResolver( | ||
TrackCache::instance().resolve(fileInfo)); | ||
TrackPointer pTrack(cacheResolver.getTrack()); | ||
const TrackPointer pTrack = cacheResolver.getTrack(); | ||
if (!pTrack) { | ||
qWarning() << "TrackDAO::addTracksAddFile:" | ||
<< "File not found" | ||
<< TrackRef::location(fileInfo); | ||
return TrackPointer(); | ||
} | ||
const TrackId trackId = pTrack->getId(); | ||
if (trackId.isValid()) { | ||
qDebug() << "TrackDAO::addTracksAddFile:" | ||
<< "Track has already been added to the database" | ||
<< trackId; | ||
return TrackPointer(); | ||
} | ||
// Keep the TrackCache locked until the id of the Track | ||
// object is known and has been updated in the cache. | ||
|
||
// Initially load the metadata for the newly created track | ||
// Initially (re-)import the metadata for the newly created track | ||
// from the file. | ||
SoundSourceProxy(pTrack).updateTrackFromSource(); | ||
if (!pTrack->isMetadataSynchronized()) { | ||
|
@@ -1241,10 +1264,16 @@ TrackPointer TrackDAO::getTrackFromDB(TrackId trackId) const { | |
return pTrack; | ||
} | ||
if (cacheResolver.getTrackCacheLookupResult() == TrackCacheLookupResult::HIT) { | ||
// Must not reload cached tracks from database! | ||
// Due to race conditions the track might have been reloaded | ||
// from the database. In this case we abort the operation and | ||
// simply return the cached Track object. | ||
return pTrack; | ||
} | ||
DEBUG_ASSERT(cacheResolver.getTrackCacheLookupResult() == TrackCacheLookupResult::MISS); | ||
DEBUG_ASSERT(pTrack->getId() == trackId); | ||
// After the Track object has been cached with an id we can safely | ||
// release the lock the cache. This is needed to reduce lock contention. | ||
cacheResolver.unlockCache(); | ||
|
||
// For every column run its populator to fill the track in with the data. | ||
bool shouldDirty = false; | ||
|
@@ -1313,10 +1342,6 @@ TrackPointer TrackDAO::getTrackFromDB(TrackId trackId) const { | |
this, SLOT(slotTrackChanged(Track*)), | ||
Qt::DirectConnection); | ||
|
||
// Finish the caching operation to release all locks before | ||
// emitting any signals. | ||
cacheResolver.unlockCache(); | ||
|
||
// BaseTrackCache cares about track trackDirty/trackClean notifications | ||
// from TrackDAO that are triggered by the track itself. But the preceding | ||
// track modifications above have been sent before the TrackDAO has been | ||
|
@@ -1333,9 +1358,12 @@ TrackPointer TrackDAO::getTrackFromDB(TrackId trackId) const { | |
TrackPointer TrackDAO::getTrack(TrackId trackId) const { | ||
//qDebug() << "TrackDAO::getTrack" << QThread::currentThread() << m_database.connectionName(); | ||
|
||
TrackCacheLocker cacheLocker( | ||
TrackCache::instance().lookupById(trackId)); | ||
TrackPointer pTrack = cacheLocker.getTrack(); | ||
// The TrackCache is only locked while executing the following line. | ||
TrackPointer pTrack = TrackCache::instance().lookupById(trackId).getTrack(); | ||
// Accessing the database is a time consuming operation that should | ||
// not be executed with a lock on the TrackCache. The TrackCache will | ||
// be locked again after the query has been executed and potential | ||
// race conditions will be resolved in getTrackFromDB() | ||
return pTrack ? pTrack : getTrackFromDB(trackId); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it correct that this is true for Mixxx tracks only, but not for third party libraries?
Why? It looks like they have their own instance of BaseTrackCache. Is there his kind of legacy?
Can we either unify this to only one instance or inherit form BaseTracKCache as a base class?
I am not sure what makes actually sense here.