-
-
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
Fix race conditions between caching reader and worker (again) #2308
Changes from all commits
e6f1a13
3e9e2ae
da5875d
322b444
69485cf
cd71c6b
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 |
---|---|---|
|
@@ -56,12 +56,12 @@ CachingReader::CachingReader(QString group, | |
// The capacity of the back channel must be equal to the number of | ||
// allocated chunks, because the worker use writeBlocking(). Otherwise | ||
// the worker could get stuck in a hot loop!!! | ||
m_stateFIFO(kNumberOfCachedChunksInMemory), | ||
m_readerStatusUpdateFIFO(kNumberOfCachedChunksInMemory), | ||
m_state(State::Idle), | ||
m_mruCachingReaderChunk(nullptr), | ||
m_lruCachingReaderChunk(nullptr), | ||
m_sampleBuffer(CachingReaderChunk::kSamples * kNumberOfCachedChunksInMemory), | ||
m_worker(group, &m_chunkReadRequestFIFO, &m_stateFIFO) { | ||
m_worker(group, &m_chunkReadRequestFIFO, &m_readerStatusUpdateFIFO) { | ||
|
||
m_allocatedCachingReaderChunks.reserve(kNumberOfCachedChunksInMemory); | ||
// Divide up the allocated raw memory buffer into total_chunks | ||
|
@@ -78,16 +78,15 @@ CachingReader::CachingReader(QString group, | |
m_freeChunks.push_back(c); | ||
} | ||
|
||
// Forward signals from worker | ||
connect(&m_worker, SIGNAL(trackLoading()), | ||
this, SIGNAL(trackLoading()), | ||
Qt::DirectConnection); | ||
connect(&m_worker, SIGNAL(trackLoaded(TrackPointer, int, int)), | ||
this, SIGNAL(trackLoaded(TrackPointer, int, int)), | ||
Qt::DirectConnection); | ||
connect(&m_worker, SIGNAL(trackLoadFailed(TrackPointer, QString)), | ||
this, SIGNAL(trackLoadFailed(TrackPointer, QString)), | ||
Qt::DirectConnection); | ||
// Handle signals from worker thread | ||
connect(&m_worker, | ||
&CachingReaderWorker::trackLoaded, | ||
this, | ||
&CachingReader::onTrackLoaded); | ||
connect(&m_worker, | ||
&CachingReaderWorker::trackLoadFailed, | ||
this, | ||
&CachingReader::onTrackLoadFailed); | ||
|
||
m_worker.start(QThread::HighPriority); | ||
} | ||
|
@@ -201,23 +200,54 @@ CachingReaderChunkForOwner* CachingReader::lookupChunkAndFreshen(SINT chunkIndex | |
} | ||
|
||
void CachingReader::newTrack(TrackPointer pTrack) { | ||
if (m_state == State::TrackLoading) { | ||
m_pNewTrack = pTrack; | ||
process(); | ||
return; | ||
} | ||
m_pNewTrack.reset(); | ||
// Feed the track to the worker as soon as possible | ||
// to get ready while the reader switches its internal | ||
// state. There are no race conditions, because the | ||
// reader polls the worker. | ||
m_worker.newTrack(pTrack); | ||
m_worker.workReady(); | ||
m_worker.newTrack(std::move(pTrack)); | ||
// Don't accept any new read requests until the current | ||
// track has been unloaded and the new track has been | ||
// loaded. | ||
m_state = State::TrackLoading; | ||
// Free all chunks with sample data from the current track. | ||
freeAllChunks(); | ||
// Emit that a new track is loading, stops the current track | ||
emit trackLoading(); | ||
} | ||
|
||
void CachingReader::onTrackLoaded( | ||
TrackPointer pTrack, | ||
int iSampleRate, | ||
int iNumSamples) { | ||
// Consume all pending messages | ||
process(); | ||
// Forward signal depending on the current state | ||
if (m_state == State::TrackLoaded) { | ||
emit trackLoaded(std::move(pTrack), iSampleRate, iNumSamples); | ||
} else { | ||
kLogger.warning() | ||
<< "No track loaded"; | ||
} | ||
} | ||
|
||
void CachingReader::onTrackLoadFailed( | ||
TrackPointer pTrack, | ||
QString reason) { | ||
// Consume all pending messages | ||
process(); | ||
// Forward signal independent of the current state | ||
emit trackLoadFailed(std::move(pTrack), reason); | ||
} | ||
|
||
void CachingReader::process() { | ||
ReaderStatusUpdate update; | ||
while (m_stateFIFO.read(&update, 1) == 1) { | ||
while (m_readerStatusUpdateFIFO.read(&update, 1) == 1) { | ||
DEBUG_ASSERT(m_state != State::Idle); | ||
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. This assertion may occur after ejecting a track and loading a new one. 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. Added comments to the wrong/closed PR: #2305 (review) This is the debug assertion that helped me discover the race condition. I refuse to remove it because it is correct unless someone provides a counterexample. 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. I have checked the old version in 3e9e2ae before the fixes and I still don't see a reason why this debug assertion is not valid? |
||
auto pChunk = update.takeFromWorker(); | ||
if (pChunk) { | ||
|
@@ -265,6 +295,11 @@ void CachingReader::process() { | |
m_readableFrameIndexRange = update.readableFrameIndexRange(); | ||
} | ||
} | ||
// To terminate the recursion a pending new track must | ||
// only be loaded if no track is currently loading! | ||
if (m_pNewTrack && m_state != State::TrackLoading) { | ||
newTrack(std::move(m_pNewTrack)); | ||
} | ||
} | ||
|
||
CachingReader::ReadResult CachingReader::read(SINT startSample, SINT numSamples, bool reverse, CSAMPLE* buffer) { | ||
|
@@ -542,6 +577,7 @@ void CachingReader::hintAndMaybeWake(const HintVector& hintList) { | |
<< "Requesting read of chunk" | ||
<< request.chunk; | ||
} | ||
DEBUG_ASSERT(m_state == State::TrackLoaded); | ||
if (m_chunkReadRequestFIFO.write(&request, 1) != 1) { | ||
kLogger.warning() | ||
<< "Failed to submit read request for chunk" | ||
|
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.
Does this work without a direct connection?
Do we have a Qt Main Loop?
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.
If we don't have an event loop then we must not use signals. Period.
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.
Done