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

Selective playlist name updates. Fixing Lp1837315 #2214

Merged
merged 17 commits into from Jul 31, 2019

Conversation

daschuer
Copy link
Member

This fixes https://bugs.launchpad.net/mixxx/+bug/1837315

The bug only occurs in the rare case, if two instances of Mixxx are running on the same DB.

@daschuer daschuer added this to the 2.2.2 milestone Jul 21, 2019

int row = 0;
for (QList<QPair<int, QString> >::const_iterator it = m_playlistList.begin();
it != m_playlistList.end(); ++it, ++row) {
int playlist_id = it->first;
QString playlist_name = it->second;

if (selected_id == playlist_id) {
if (playlistId == playlist_id) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The same name in camel-case and snake case notation. Who should understand this code?

@uklotzde
Copy link
Contributor

Modifying the database concurrently is not supported. Why should we fix undefined behaviour for just this single use case and ignore all others?

Without a generic concept how to deal with this situation I don't see no value in such an attempt. Even worse it requires additional comments and introduces inconsistencies on the code level.

break;
}
}
break;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a very strange way to fetch just the first element from the result set. It took me some time until I noticed that the outer loop is actually not a loop at all.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I think it is my leak of knowledge how to use the Qt API correct in this case. I we haves such constructs in other places where we loop through a table even though we expect only one or no rows. How can I fix this?

Copy link
Contributor

Choose a reason for hiding this comment

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

We expect at most a single row here. This can easily be expressed through the code.

DEBUG_ASSERT(playlistTableModel.rowCount() <= 1);
if (playlistTableModel.rowCount() > 0) {
...

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, yes that's easy. Can we also fix the while loop above?

for (auto it = m_playlistList.begin();
it != m_playlistList.end(); ++it) {
if (it->first == playlist_id) {
it->second = QString("%1 (%2) %3").arg(name,
Copy link
Contributor

Choose a reason for hiding this comment

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

This formatting code is now duplicated.

void buildPlaylistList();
void decorateChild(TreeItem *pChild, int playlist_id);
void buildPlaylistList() override;
void updatePlaylistList(int playlist_id) override;
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand what the difference between buildPlaylistList and updatePlaylistList is. Looking at their implementation both functions do fundamentally different things despite their very similar names.

Copy link
Member Author

Choose a reason for hiding this comment

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

buildPlaylistList(), the original function clears the list of playlists m_playlistList and populates it again with fresh data from the database. This function was also used to refresh single tree view items, without refreshing the whole tree view with the recently fetched database data.
updatePlaylistList(int playlist_id) now only fetches the requested playlist and updates this in m_playlistList and GUI.
This way both lists are still consistent and immune against external changes, the root cause of the crash.

Should I rename updatePlaylistList(int playlist_id) to refreshPlaylistInPlaylistList((int playlist_id)?
Or do you have a better idea to rename both?

Copy link
Contributor

Choose a reason for hiding this comment

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

The whole naming is confusing, i.e. "Playlist" vs. "List". But leave it as it is to keep the impact low.

refresh/reloadPlaylistInPlaylistList works for me.

List -> Listing, Summary, Overview, ... someone else should wrap their head around this.

break;
}
}
break;
Copy link
Contributor

Choose a reason for hiding this comment

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

Once again a non-loop in disguise

@daschuer
Copy link
Member Author

Modifying the database concurrently is not supported. Why should we fix undefined behaviour for just this single use case and ignore all others?

Because of the way this bug happens to me. First it was just a crash that strikes me and I had no Idea why. It took me a while to figure out that this is related to two Mixxx instances. I often start accidantally two Mixxx instances. Since I know now why it crashed, I like to have that fixed.
During seeking for the root cause I have found this solution of the two inconsistent tables in the GUI and the model. The change makes sure both tables are consistent and has also a small perfomance benefit. I am open to discuss the best solution for it.

Without a generic concept how to deal with this situation I don't see no value in such an attempt. Even worse it requires additional comments and introduces inconsistencies on the code level.

I wanted to keep the scope of this original crash fix limited. The concept is: Mixxx is the a the single DB user but it should at least not crash in the exceptional case of two concurrent access.

What are your ideas?

@daschuer
Copy link
Member Author

OK, I hope it is better now.

}
}

void PlaylistFeature::reloadPlaylistInPlaylistList(int playlist_id) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use camel case for new code, that's what the style guide recommends.


DEBUG_ASSERT(playlistTableModel.rowCount() <= 1);
if (playlistTableModel.rowCount() > 0) {
for (auto it = m_playlistList.begin();
Copy link
Contributor

Choose a reason for hiding this comment

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

This loop now appears multiple times. Can't we extract it in a function named replacePlaylistLabel(id, label)? That would improve readability.

Copy link
Contributor

@uklotzde uklotzde Jul 22, 2019

Choose a reason for hiding this comment

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

A better name for m_playlistList would actually be m_playlistLabels.

I noticed this only after your added the function for formatting the label. Before it wasn't obvious and hidden behind a pile of code. Inspecting the actual type in the header was just too far away.

And out of a sudden an appropriate naming appears ;)

Copy link
Contributor

Choose a reason for hiding this comment

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

buildPlaylistList -> createPlaylistLabels()
reloadPlaylistInPlaylistList -> updatePlaylistLabel(int playlistId)
How much effort would it be to change the naming?

Copy link
Member Author

Choose a reason for hiding this comment

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

Eclipse has a rename feature, so it should be no deal.

@daschuer
Copy link
Member Author

done

void BasePlaylistFeature::updateChildModel(int selected_id) {
buildPlaylistList();
void BasePlaylistFeature::updateChildModel(int changedPlaylistId) {
// the following call fetches whole m_playlistList.
Copy link
Contributor

Choose a reason for hiding this comment

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

The comments is outdated and doesn't really help:

  • m_playlistList
  • How updatePlaylistLabel() actually works should not matter here

void BasePlaylistFeature::updateChildModel(int changedPlaylistId) {
// the following call fetches whole m_playlistList.
// Anything may have change due to a concurrent Mixxx instance.
updatePlaylistLabel(changedPlaylistId);
Copy link
Contributor

Choose a reason for hiding this comment

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

The parameter changedPlaylistId is too verbose. This functions doesn't need to care what happened to the playlist before. It is instructed to update the tree item of a particular playlistId and that's it.

The functions should actually be named updatePlaylistItem(), because it updates the entire item and not only the label.

The code is still more complex than it needs to be:

QString updatedLabel = updatePlaylistLabel(playlistId);
TreeItem* playlistItem = findPlaylistItem(playlistId);
VERIFY_OR_DEBUG_ASSERT(playlistItem) {
   return;
}
DEBUG_ASSERT(playlistItem->getData() == playlistId);
item->setLabel(updatedLabel);
decorateChild(playlistItem, playlistId);

Copy link
Contributor

@uklotzde uklotzde Jul 27, 2019

Choose a reason for hiding this comment

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

Is m_playlistLabels actually needed if all this information is stored redundantly in the TreeItems?

Copy link
Contributor

Choose a reason for hiding this comment

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

See also: CrateFeature and CrateSummary, no redundant and error-prone extra bookkeeping needed.

@daschuer
Copy link
Member Author

OK, I have managed to dispose m_playlistLable finally.
A bit to much for a bugfix, but the root cause is entirely fixed now.

void BasePlaylistFeature::updateChildModel(int selected_id) {
buildPlaylistList();
void BasePlaylistFeature::updateChildModel(int playlistId) {
QString playlistLable = fetchPlaylistLabel(playlistId);
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: Lable -> Label
Please check all occurrences

virtual QModelIndex constructChildModel(int selected_id);
virtual void updateChildModel(int selected_id);
virtual void clearChildModel();
virtual void buildPlaylistList() = 0;
virtual void decorateChild(TreeItem *pChild, int playlist_id) = 0;
virtual void createPlaylistLabels(QList<IdAndLabel>* pPlaylistLabels) = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

The QList can be returned by-value

@daschuer
Copy link
Member Author

done

if (!pTreeItem->hasChildren()) { // leaf node
bool ok;
int playlistId = pTreeItem->getData().toInt(&ok);
if (ok) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This conversion should never fail: VERIFY_OR_DEBUG_ASSERT(ok) { continue; }

it != m_playlistList.end(); ++it, ++row) {
int playlist_id = it->first;
QString playlist_name = it->second;
for (auto it = playlistLabels.constBegin();
Copy link
Contributor

Choose a reason for hiding this comment

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

Shorter and simpler variant:

for (const IdAndLabel& idAndLabel : createPlaylistLabels()) {

The actual container type doesn't matter for the iteration, so why depend on it.

I would even use const auto&& here and let the compiler do all the type checking. But I know that you prefer old-school explicit type declarations ;)

Copy link
Contributor

Choose a reason for hiding this comment

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

But don't forget to the row++ in the loop body. That's the only drawback.

@uklotzde
Copy link
Contributor

Patching an unsuitable design by piling up more code on top of it doesn't work out in the long term. What started as an attempt to fix a rare bug by adding more code has become a real improvement that should prevent future errors. That's the right approach!

@daschuer
Copy link
Member Author

done

@uklotzde uklotzde merged commit 0295027 into mixxxdj:2.2 Jul 31, 2019
@daschuer
Copy link
Member Author

Thank you for review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants