Skip to content
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

GlobalTrackCache: Fix edge cases and tests #2293

Merged
merged 19 commits into from
Sep 30, 2019
Merged

GlobalTrackCache: Fix edge cases and tests #2293

merged 19 commits into from
Sep 30, 2019

Conversation

uklotzde
Copy link
Contributor

I still experience a crash caused by GlobalTrackCache once in a while when closing Mixxx. It happens very infrequently, mostly after loading just a single or a few tracks. These crashes may still be unsolved, nevertheless I discovered some other issues during my investigations:

  • Updated or removed outdated comments
  • Ensure that all cache items are only moved, never copied
  • Verify that functors are called only once
  • Fixed edge cases when evicting tracks that might occur due to expected race conditions
  • Disconnect receivers and block signals in GlobalTrackCache instead of in the Library code.
  • Fixed a memory leak in the concurrency test. Track objects were neither evicted nor deleted because no main event loop is executed! Note: deleteLater() is still not invoked even when calling QCoreApplication::processEvents() periodically! I have added a workaround for Qt >= 5.6.0 in the code, but this memory leak is still present for earlier versions (only in tests).

@uklotzde uklotzde added this to the 2.2.3 milestone Sep 22, 2019
@uklotzde
Copy link
Contributor Author

Next try, functors need to be copyable.

@uklotzde
Copy link
Contributor Author

I was not able to find out which test actually caused the crash and had to add a workaround. The WPushButtonTest can't be the culprit. Startup and shutdown of application components during tests don't seem to be sane.

@uklotzde uklotzde changed the title GlobalTrackCache: Fix edge cases and tests [WiP] GlobalTrackCache: Fix edge cases and tests Sep 22, 2019
@uklotzde uklotzde changed the title [WiP] GlobalTrackCache: Fix edge cases and tests GlobalTrackCache: Fix edge cases and tests Sep 22, 2019
@uklotzde
Copy link
Contributor Author

The crash during the tests is hopefully fixed now. The insights gained from this analysis and the resulting fix might also eliminate the rare crashes on shutdown. Only time will tell.

src/test/globaltrackcache_test.cpp Outdated Show resolved Hide resolved
src/test/librarytest.h Outdated Show resolved Hide resolved
DEBUG_ASSERT(plainPtr->signalsBlocked());
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
if (plainPtr->thread()->loopLevel() > 0) {
plainPtr->deleteLater();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://doc.qt.io/qt-5/qthread.html:

Note: This can only be called within the thread itself, i.e. when it is the current thread.

Is this guaranteed?

Copy link
Contributor Author

@uklotzde uklotzde Sep 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Cache entries are always evicted on the main (event loop) thread, and then the track pointer goes out of scope. ...or maybe not. I need to check this!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a correct, more explicit solution. The tests now provide a function pointer to override the default QObject::deleteLater() deletion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new solution is independent of the Qt version.

src/track/globaltrackcache.cpp Outdated Show resolved Hide resolved
@uklotzde
Copy link
Contributor Author

In a branch without these changes the spurious crashes are back. Seems like the fixes work as expected!

Copy link
Member

@daschuer daschuer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes are looking sensible.
Unfortunately I do not understand which part is the crux for the crash fix here?
Can you give some additional hints.

src/test/globaltrackcache_test.cpp Show resolved Hide resolved
void operator()(Track* pTrack) const;

private:
deleteTrackFn m_deleteTrack;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about deleteTrackFn m_deleteTrackFn ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_typeName? The name of the function is deleteTrack, the corresponding type is deleteTrackFn.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have stumbled over this because it can also be bool m_deleteTrack;
The idea was to make it obvious from the variable name that this is a function pointer.

src/track/globaltrackcache.cpp Outdated Show resolved Hide resolved
if (trackById != m_tracksById.end()) {
DEBUG_ASSERT(trackById->second->getPlainPtr() == plainPtr);
if (trackById != m_tracksById.end() &&
trackById->second->getPlainPtr() == plainPtr) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if this fails?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to check this again

@uklotzde
Copy link
Contributor Author

uklotzde commented Sep 28, 2019

Possible crash fixes:

  • evict() -> tryEvict(): Due to a race conditions during evictAndSave() the wrong pointer could be removed from the maps. Renamed the function to better reflect these circumstances. The corresponding DEBUG_ASSERTION never triggered during the tests, but would fail now.
  • Invoke QObject::disconnect() when evicting a track from the cache (+ block signals as before, just in case)
  • Invoke QCoreApplication::processEvents() before destroying the cache singleton to finish the pending deletion of track objects

Not sure which of these actually fixed the issue. The tests never worked as intended and the issues slipped through unnoticed.

@daschuer
Copy link
Member

LGTM, thank you.

@daschuer daschuer merged commit 9688e04 into mixxxdj:2.2 Sep 30, 2019
@uklotzde uklotzde deleted the 2.2_globaltrackcache branch September 30, 2019 07:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants