Skip to content

Delete workload-related things if the rest of the operation was unsuccessful #47110

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

Merged
merged 3 commits into from
Feb 28, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Text.Json;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.NuGetPackageDownloader;
Expand Down Expand Up @@ -300,6 +302,7 @@ void InstallPackage(PackageId packageId, string packageVersion, string targetFol
{
string packagePath = null;
string tempBackupDir = null;
bool directoryExists = Directory.Exists(targetFolder) && Directory.GetFileSystemEntries(targetFolder).Any();

transactionContext.Run(
action: () =>
Expand All @@ -319,7 +322,7 @@ void InstallPackage(PackageId packageId, string packageVersion, string targetFol
}

// If target directory already exists, back it up in case we roll back
if (Directory.Exists(targetFolder) && Directory.GetFileSystemEntries(targetFolder).Any())
if (directoryExists)
{
tempBackupDir = Path.Combine(_tempPackagesDir.Value, $"{packageId} - {packageVersion}-backup");
if (Directory.Exists(tempBackupDir))
Expand All @@ -336,8 +339,18 @@ void InstallPackage(PackageId packageId, string packageVersion, string targetFol
{
if (!string.IsNullOrEmpty(tempBackupDir) && Directory.Exists(tempBackupDir))
{
// Delete the folder first to account for new files added
Directory.Delete(targetFolder, recursive: true);
FileAccessRetrier.RetryOnMoveAccessFailure(() => DirectoryPath.MoveDirectory(tempBackupDir, targetFolder));
}
else if (!directoryExists && Directory.Exists(targetFolder))
{
// If files are copied to the targetFolder, then another operation in this transaction fails, we want to
// ensure that we roll back to the prior state. In this case, the directory did not exist (or at least
// didn't have files in it), so roll back to that state. A folder without files should not exist for
// our purposes, so we can ignore the possibility of there having been an empty folder before.
Directory.Delete(targetFolder, recursive: true);
Copy link
Preview

Copilot AI Feb 25, 2025

Choose a reason for hiding this comment

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

In the cleanup rollback logic, the code deletes the target directory without checking if it exists. To prevent a potential DirectoryNotFoundException, consider adding a conditional check using Directory.Exists(targetFolder) before calling Directory.Delete.

Copilot uses AI. Check for mistakes.

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 is checked via directoryExists

Copy link
Member

Choose a reason for hiding this comment

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

Theoretically, directoryExists could be false if the directory existed but was empty if I'm reading the call right. That isn't related to what copilot is chatting about but just something I noticed.

Copy link
Member

Choose a reason for hiding this comment

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

But if the directory did not exist, we'd still try to delete it. That would throw.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are five cases that cover all possibilities:
Directory exists and is not empty, crash after line 332: directoryExists is true, but this is overwritten via the first 'if', so this doesn't really change anything (rollback is already correct)
Directory exists and is not empty, crash before line 332: directoryExists is true, and neither this new rollback code nor the old rollback code run. Rollback is already correct.
Directory exists and is empty: directoryExists is false. We delete whatever we find. The directory should not exist if it's empty, so though this is a minor change, I think it's slightly beneficial
Directory does not exist, crash after line 332: directoryExists is false, and this deletes the new folder copied to this location. This is currently broken, fixed by this PR
Directory does not exist, crash before line 332: directoryExists is false, and this throws erroneously.

I actually did test that last scenario, and it didn't cause any problems. I looked into why that doesn't cause actually matter in this case. This is the last part of the rollback code, so it doesn't affect this particular rollback action. Each rollback action is executed separately in a try/catch by CliTransaction, so it catches this exception. It then executes RollbackFailed, which in our case just prints out a message and continues, so the exception is essentially ignored, and this bug is roughly irrelevant until we change any part of this just slightly 🙂

I think that's still worth changing, though it's interesting that we have enough try/catches around this that it actually doesn't matter until we change the code again.

}
},
cleanup: () =>
{
Expand Down