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

Fix writing of track metadata on Windows #4586

Merged
merged 11 commits into from
Dec 30, 2021
Merged

Conversation

daschuer
Copy link
Member

@daschuer daschuer commented Dec 27, 2021

This fixes https://bugs.launchpad.net/mixxx/+bug/1955314 and https://bugs.launchpad.net/mixxx/+bug/1955331.

This is a mayor bugfix that should be released soon. Because the track metadata was not exported on windows when the track has not been loaded into a deck. Since the user assumes that the metadata is written, there is a risk of data loss. This was discovered in the first attempt for a fix here: #4572

The fix makes use of the ReplaceFileW() API function that is recommended instead of the deprecated transition feature for atomic file operations. ReplaceFileW() avoids virus scanner and indexer locking and keeps the file creation time at its old value.

The refactoring part has been issued here #4584 for the main branch.

@daschuer daschuer requested a review from uklotzde December 27, 2021 22:10
<< "by"
<< m_tempFileName
<< "because it is used by another process";
Sleep(kWindowsSharingViolationsSleepMs); // Note: Capital S for Windows API Sleep()
Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't you use QThread::msleep() here?

// After Mixxx has closed the track file, the indexer or virus scanner
// might kick in and fail ReplaceFileW() with a sharing violation when
// replacing the original file with the one with the updated metadata.
const int kWindowsSharingViolationsRetries = 5;
Copy link
Contributor

Choose a reason for hiding this comment

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

kWindowsSharingViolationMaxRetries

// might kick in and fail ReplaceFileW() with a sharing violation when
// replacing the original file with the one with the updated metadata.
const int kWindowsSharingViolationsRetries = 5;
const int kWindowsSharingViolationsSleepMs = 100;
Copy link
Contributor

Choose a reason for hiding this comment

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

kWindowsSharingViolationSleepBeforeNextRetryMillis

return false;
case ERROR_SHARING_VIOLATION:
// The process cannot access the file because it is being used by another process.
kLogger.critical()
Copy link
Contributor

Choose a reason for hiding this comment

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

Not critical if a retry is following.

return false;
}
}
break;
Copy link
Contributor

Choose a reason for hiding this comment

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

It is difficult to recognize that this break belongs to the loop. Is it actually needed?

@@ -30,6 +34,14 @@ const QString kSafelyWritableTempFileSuffix = QStringLiteral("_temp");
// potential failures caused by exceeded path length.
const QString kSafelyWritableOrigFileSuffix = QStringLiteral("_orig");

#ifdef __WINDOWS__
// After Mixxx has closed the track file, the indexer or virus scanner
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment belongs to the code where these constants are actually used, but not to these constants. Mentioning why ReplaceFileW() is needed here but not in the context were it is actually invoked is confusing.

@@ -361,6 +361,12 @@ SoundSourceProxy::exportTrackMetadataBeforeSaving(Track* pTrack, UserSettingsPoi
pTrack->updateStreamInfoFromSource(
pAudioSource->getStreamInfo());
DEBUG_ASSERT(pTrack->hasStreamInfoFromSource());
// Make sure that the track file is closes after reading
Copy link
Contributor

Choose a reason for hiding this comment

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

multiple typos

@@ -731,6 +737,7 @@ mixxx::AudioSourcePointer SoundSourceProxy::openAudioSource(
// before exporting metadata. In this case the caller (this class)
// is responsible for updating the stream info if needed.
if (!m_pTrack) {
// TODO() It is surprising to receive here a m_pSoundSource;
Copy link
Contributor

Choose a reason for hiding this comment

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

  • TODO is not a function
  • "receive"?
  • semicolon to end a comment?

@daschuer
Copy link
Member Author

Done

@ronso0 ronso0 added the changelog This PR should be included in the changelog label Dec 28, 2021
@@ -731,6 +737,7 @@ mixxx::AudioSourcePointer SoundSourceProxy::openAudioSource(
// before exporting metadata. In this case the caller (this class)
// is responsible for updating the stream info if needed.
if (!m_pTrack) {
// TODO: It is surprising that openAudioSource returns a m_pSoundSource
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it suprising?? SoundSource is an extension of both AudioSource and MetadataSource, basic OO design.

Copy link
Contributor

Choose a reason for hiding this comment

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

This should work and fix the bug with the open file:

m_pAudioSource = m_pSoundSource;
return m_pAudioSource;

Copy link
Member Author

Choose a reason for hiding this comment

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

I have already fixed the underlying issue in #4584 please have a look.

@@ -361,6 +361,12 @@ SoundSourceProxy::exportTrackMetadataBeforeSaving(Track* pTrack, UserSettingsPoi
pTrack->updateStreamInfoFromSource(
pAudioSource->getStreamInfo());
DEBUG_ASSERT(pTrack->hasStreamInfoFromSource());
// Make sure that the track file is closed after reading.
Copy link
Contributor

Choose a reason for hiding this comment

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

This additional code should become obsolete with the fix I just proposed.

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 still needed:

proxy.closeAudioSource();

Copy link
Member Author

Choose a reason for hiding this comment

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

I would like to keep this as an hot fix solution with a minimum impact.

The rest is fixed in #4584

Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer a proper fix here.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am not going to merge a hack with a wrong comment.

Copy link
Member Author

Choose a reason for hiding this comment

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

Why do you think the comment is wrong? Add least it is working.

But I don't mind to merge your ideas.
Can you issue a PR against my branch?

Thank you.

Copy link
Member Author

Choose a reason for hiding this comment

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

The alternative would be to merge #4584 to this branch. This is IMHO the proper fix, but touches more code.

Copy link
Contributor

Choose a reason for hiding this comment

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

I haven't reviewed #4584 yet and I cannot promise to do it soon.

Copy link
Contributor

Choose a reason for hiding this comment

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

daschuer#72

Moreover the build failed on Linux with Clang due to a wrong preprocessor directive. Fixed.

If the AudioSource is opened the internal pointer must be initialized
to ensure that it could be closed later!
Fixes the build on Linux with CLang.
@uklotzde
Copy link
Contributor

uklotzde commented Dec 29, 2021

This important bugfix should be mentioned in the CHANGELOG. Ok, already labelled.

lp1955331: Preserve class invariants of SoundSourceProxy
@daschuer
Copy link
Member Author

We have got the confirmation of that it is working:
https://bugs.launchpad.net/mixxx/+bug/1955314

@daschuer daschuer requested a review from uklotzde December 30, 2021 12:16
@daschuer daschuer added this to the 2.3.2 milestone Dec 30, 2021
Copy link
Contributor

@uklotzde uklotzde left a comment

Choose a reason for hiding this comment

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

Thank you! LGTM

I'll merge to main

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog This PR should be included in the changelog major bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants