8000 Fixed issue #970 by ThomasBarnekow · Pull Request #971 · libgit2/libgit2sharp · GitHub
[go: up one dir, main page]

Skip to content

Fixed issue #970 #971

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 1 commit into from
Feb 23, 2015
Merged

Conversation

ThomasBarnekow
Copy link
Contributor

This pull request fixes issue #970.

@Therzok
Copy link
Member
Therzok commented Feb 21, 2015

LGTM. I was wondering is XUnit supports any kind of debug output so we can tell the tester at the end: yo, something screwed up, you have to manually delete the files (residual files can cause subsequent failures if same hash ends up used).

8000

{
File.Delete(file);
}
catch (Exception)
Copy link
Member

Choose a reason for hiding this comment

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

I'd rather us to be a little more specific here. Could you please amend your commit (ie. not fixing this through another additional commit) and make this rather catch UnauthorizedAccessException and this is what #970 is about?

@nulltoken
Copy link
Member

While you're tweaking your commit, could you please reword your commit message so that the reader can understand what the commit does by reading through git log?

You can peek at caded6e for a recent great commit message.

You can also see https://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message for some good background details.

@ThomasBarnekow
Copy link
Contributor Author

@Therzok I'm pretty new to xUnit (only used NUnit and JUnit before), so I can't answer your question. But it would certainly be a good idea to either provide a message or attempt a cleanup to at least limit the number of leftover files or directories.

@nulltoken I caught an Exception rather than an UnauthorizedAccessException for two reasons. One, I'm not sure whether an UnauthorizedAccessException is the only thing that can happen at this point. Two, we should not care which Exception is thrown because no Exception at this point should make a test fail. Can amend the commit to tweak the message, though.

@nulltoken
Copy link
Member

@ThomasBarnekow Let's walk this path step by step. We know that UnauthorizedAccessException pops up on your system, let's work around this known issue rather than swallowing any kind of Exception.

catch (UnauthorizedAccessException ex)
{
Trace.WriteLine(GetErrorMessage("file", file, ex));
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nulltoken In this amended commit, both exceptions are caught separately and will produce an error message that differentiates between the two types of commits (at least by saying what type of exception was thrown).

@ThomasBarnekow
Copy link
Contributor Author

@nulltoken You might still want to have a look at those messages as this might provide some insight as to what other issue might be behind this.

Here's an interesting example because both UnauthorizedAccessException and IOException are thrown.

Output from LibGit2Sharp.Tests.CloneFixture.CanClone(url: "http://github.com/libgit2/TestGitRepository"):
  The file 'C:\Visual Studio 2010\Projects\GitHub\LibGit2Sharp\LibGit2Sharp.Tests\bin\Debug\TestRepos\mcztf4ly.b0f\.git\objects\pack\pack-773b425dab536d28aaeaf2b8f310c9c25f256087.idx' could not be deleted due to a 'System.UnauthorizedAccessException'!
  Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files.
  Known and common causes include:
  - Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)
  - Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus)


  The file 'C:\Visual Studio 2010\Projects\GitHub\LibGit2Sharp\LibGit2Sharp.Tests\bin\Debug\TestRepos\mcztf4ly.b0f\.git\objects\pack\pack-773b425dab536d28aaeaf2b8f310c9c25f256087.pack' could not be deleted due to a 'System.IO.IOException'!
  Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files.
  Known and common causes include:
  - Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)
  - Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus)


  The directory 'C:\Visual Studio 2010\Projects\GitHub\LibGit2Sharp\LibGit2Sharp.Tests\bin\Debug\TestRepos\mcztf4ly.b0f\.git\objects\pack' could not be deleted due to a 'System.IO.IOException'!
  Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files.
  Known and common causes include:
  - Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)
  - Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus)


  The directory 'C:\Visual Studio 2010\Projects\GitHub\LibGit2Sharp\LibGit2Sharp.Tests\bin\Debug\TestRepos\mcztf4ly.b0f\.git\objects' could not be deleted due to a 'System.IO.IOException'!
  Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files.
  Known and common causes include:
  - Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)
  - Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus)


  The directory 'C:\Visual Studio 2010\Projects\GitHub\LibGit2Sharp\LibGit2Sharp.Tests\bin\Debug\TestRepos\mcztf4ly.b0f\.git' could not be deleted due to a 'System.IO.IOException'!
  Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files.
  Known and common causes include:
  - Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)
  - Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus)


  The directory 'C:\Visual Studio 2010\Projects\GitHub\LibGit2Sharp\LibGit2Sharp.Tests\bin\Debug\TestRepos\mcztf4ly.b0f' could not be deleted due to a 'System.IO.IOException'!
  Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files.
  Known and common causes include:
  - Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)
  - Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus)

On an unrelated note, there might be some potential for streamlining the output. When an exception is thrown for a file or directory, the same message is repeated for all ancestors.

@nulltoken
Copy link
Member

@ThomasBarnekow Neat! It may worth it to actually also render the exception message.

Something is keeping a hold on those objects. How do you run your tests? Through an IDE (which one, which version, which test runner)? Through an external UI?

Can you repro this behavior by running the build.libgit2sharp.cmd ?

@ThomasBarnekow
Copy link
Contributor Author

@nulltoken I've just run another test to compare the output with the previous test's output. Now, I've seen this exception (I believe for the first time):

LibGit2Sharp.Tests.CloneFixture.CloningAnUrlWithoutPathThrows : System.UnauthorizedAccessException : Access to the path 'C:\Visual Studio 2010\Projects\GitHub\LibGit2Sharp\LibGit2Sharp.Tests\bin\Debug\TestRepos\btaacagu.lor' is denied.
Stack Trace:
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.FileSystemEnumerableIterator`1.CommonInit()
   at System.IO.FileSystemEnumerableIterator`1..ctor(String path, String originalUserPath, String searchPattern, SearchOption searchOption, SearchResultHandler`1 resultHandler, Boolean checkHost)
   at System.IO.Directory.InternalGetFileDirectoryNames(String path, String userPathOriginal, String searchPattern, Boolean includeFiles, Boolean includeDirs, SearchOption searchOption, Boolean checkHost)
   at System.IO.Directory.InternalGetFiles(String path, String searchPattern, SearchOption searchOption)
   at LibGit2Sharp.Tests.TestHelpers.DirectoryHelper.DeleteDirectory(String directoryPath) in C:\Visual Studio 2010\Projects\GitHub\LibGit2Sharp\LibGit2Sharp.Tests\TestHelpers\DirectoryHelper.cs:line 57
   at LibGit2Sharp.Tests.TestHelpers.BaseFixture.Dispose() in C:\Visual Studio 2010\Projects\GitHub\LibGit2Sharp\LibGit2Sharp.Tests\TestHelpers\BaseFixture.cs:line 212
   at Xunit.Sdk.LifetimeCommand.Execute(Object testClass)
   at Xunit.Sdk.ExceptionAndOutputCaptureCommand.Execute(Object testClass)

The following statement produces that exception:

string[] files = Directory.GetFiles(directoryPath);

It looks like any file system operation can potentially throw an exception (IOException or UnauthorizedAccessException). Therefore, I'm thinking that we should probably fix this a little differently. I'd propose to catch the exceptions in the BaseFixture.Dispose() method, like so:

        public virtual void Dispose()
        {
            foreach (string directory in directories)
            {
                try
                {
                    DirectoryHelper.DeleteDirectory(directory);
                }
                catch (IOException ex)
                {
                    Trace.WriteLine(GetErrorMessage("directory", directory, ex));
                }
                catch (UnauthorizedAccessException ex)
                { 
                    Trace.WriteLine(GetErrorMessage("directory", directory, ex));
                }
            }
        }

This assumes the GetErrorMessage() method I added to DirectoryHelper is moved to BaseFixture (and it might then be simplified because we are only talking about directories).

DirectoryHelper.DeleteDirectory() would no longer catch any exception or write trace messages. This would only be done in the Dispose() method. This would also simplify the output, like so:

Output from LibGit2Sharp.Tests.SmartSubtransportFixture.CustomSmartSubtransportTest(scheme: "https", url: "https://github.com/libgit2/TestGitRepository"):
  The directory 'C:\Visual Studio 2010\Projects\GitHub\LibGit2Sharp\LibGit2Sharp.Tests\bin\Debug\TestRepos\ti51urck.xiz' could not be deleted due to a 'System.IO.IOException'!
  Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files.
  Known and common causes include:
  - Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)
  - Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus)

Output from LibGit2Sharp.Tests.FilterBranchFixture.CanCustomizeTheNamespaceOfBackedUpRefs(backupRefsNamespace: "refs/rewritten/"):
  The directory 'C:\Visual Studio 2010\Projects\GitHub\LibGit2Sharp\LibGit2Sharp.Tests\bin\Debug\TestRepos\2j3kzxtj.sux' could not be deleted due to a 'System.IO.IOException'!
  Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files.
  Known and common causes include:
  - Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)
  - Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus)

I'll wait with further commits until I've seen your thoughts on this. Maybe @Therzok would like to chime in as well.

@nulltoken
Copy link
Member

Something is keeping a hold on those objects. How do you run your tests? Through an IDE (which one, which version, which test runner)? Through an external UI?

Can you reproduce this behavior by running the build.libgit2sharp.cmd?

Can you try whitelisting the LibGit2Sharp directory path in you anti-virus?

@ThomasBarnekow
Copy link
Contributor Author

@nulltoken I'm using Visual Studio 2010 and the xUnit GUI test runner which I've added as an external tool so I can select the LibGit2Sharp.Tests project and then run tests by clicking Tools - xUnit GUI Test. Here's the configuration:

Title: xUnit GUI Test
Command: C:\Visual Studio 2010\Projects\GitHub\LibGit2Sharp\packages\xunit.runners.1.9.2\tools\xunit.gui.clr4.exe
Arguments: $(BinDir)$(TargetName)$(TargetExt)
Initial directory: $(BinDir)

And yes, we should render the exception message, in particular if we decide to only catch exceptions in the BaseFixture.Dispose() method (which is the only one calling `DirectoryHelper.DeleteDirectory()``.

Let me try running the build.libgit2sharp.cmd. I'll tell you what happened.

@ThomasBarnekow
Copy link
Contributor Author

@nulltoken just ran build.libgit2sharp.cmd and saw eight error messages (all caught, so no failed tests).

Will see whether I can whitelist the a directory in my anti-virus.

@ThomasBarnekow
Copy link
Contributor Author

@nulltoken While I'm not sure whether that covered all the security tools installed on my machine, I've at least configured an exception in my Symantec AntiVirus. Running build.libgit2sharp.cmd again showed a number of our error messages again, though.

What might be causing this, however, could be a totally different thing. I'm using TortoiseGit, which also scans Git repositories to produce icon overlays. This might get in the way of removing these folders.

@ThomasBarnekow
Copy link
Contributor Author

Just uninstalled TortoiseGit (because there is no way to deactivate the scan) and temporarily deactivated all malware and virus scanners I see on my machine. Nevertheless, the same exceptions are thrown (but caught).

@nulltoken
Copy link
Member

Thanks for investigated this further! I cannot reproduce this through xUnit Gui runner. Hrmpf... 😕

Let's move forward. Could you please amend your commit by adding the exception message so we can get this in? Once this is done, could you also please rebase your branch onto the latest vNext so that we can fast forward merge it?

@ThomasBarnekow
Copy link
Contributor Author

I made some further changes to the DirectoryHelper.DeleteDirectory() method and solved the problem. It now handles exceptions properly and manages to successfully cleanup the directories!

For the rare case that an UnauthorizedAccessException is thrown more than once, the BaseFixture.Dispose() method should catch this and write an error message.

I'll amend and rebase as you said.

@ThomasBarnekow
Copy link
Contributor Author

I'm now pretty sure that TortoiseGit was causing most of my problems:

  • While exceptions were still thrown after I uninstalled TortoiseGit (and rebooted), it was possible to remove the directories and have a clean Test Repos parent directory after having run all tests (mind you with the changes I made to the DirectoryHelper and BaseFixture classes).
  • With TortoiseGit installed, however, 20 or so directories were not deleted. I've also seen a test case fail because it could not write to its repository, i.e., TortoiseGit even gets in the way of those test cases.

Fortunately, TortoiseGit can also be configured to exclude the bin folder of LibGit2Sharp.Tests. I've therefore added that to the list of known issues in the error message.

@nulltoken
Copy link
Member

Unfortunately, I'm not a TortoiseGit user. @csware Any idea?

@ThomasBarnekow
Copy link
Contributor Author

@nulltoken TortoiseGit uses a background process called TGitCache to analyze the status of git repositories and then show corresponding icon overlays in Explorer. This interferes with the DirectoryHelper.DeleteDirectory() method or potentially tests deleting or writing to repositories.

As I said, this can be configured so it no longer interferes (although the DirectoryHelper.DeleteDirectory() and BaseFixture.Dispose() no deal with it even if it is not configured correctly. In that case, there will be leftover folders in the TestRepos folder, though.

catch (UnauthorizedAccessException ex)
{
Trace.WriteLine(GetErrorMessage(directory, ex));
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The UnauthorizedAccessException must be caught because it is still thrown by DirectoryHelper.DeleteDirectory() if it is caught a second time.

@csware
Copy link
csware commented Feb 22, 2015

@ThomasBarnekow Which version of TortoiseGit?

"{0}- Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus)" +
"{0}- TortoiseGit (change the 'Icon Overlays' settings, e.g., adding the bin folder of LibGit2Sharp.Tests to 'Exclude paths' and appending an '*' to exclude all subfolders as well)",
Environment.NewLine, Path.GetFullPath(path), ex.GetType(), ex.Message);
}
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've moved this method over from the DirectoryHelper class because it is no longer required there. I've also added TortoiseGit as a known cause for the error.

@ThomasBarnekow
Copy link
Contributor Author

@csware I'm using TortoiseGit 1.8.13.0.

@@ -71,16 +72,37 @@ public static void DeleteDirectory(string directoryPath)
File.SetAttributes(directoryPath, FileAttributes.Normal);
try
{
Directory.Delete(directoryPath, false);
Directory.Delete(directoryPath, true);
Copy link
Member

Choose a reason for hiding this comment

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

This also looks unrelated. Please revert this change.

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 not unrelated because files are no longer deleted individually. Reverting this change would break the fix.

Copy link
Member

Choose a reason for hiding this comment

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

Oh! I missed the removal of File.Delete() above.

What kind of exception would we write out to the trace would one file be held by another process while recursively removing a directory up the hierarchy? Would it clearly specify the name of the file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would be the anUnauthorizedAccessException with the exact file name. This is done in the BaseFixture.Dispose() method.

This is also the only place were we really need to write out anything to the trace. If DirectoryHelper.DeleteDirectory() doesn't throw, it successfully removed the directory, possibly retrying once or twice.

@ThomasBarnekow
Copy link
Contributor Author

@nulltoken All logging is now turned on.

@nulltoken
Copy link
Member

@ThomasBarnekow You'll find below a counter proposal

diff --git a/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs b/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs
index fdb8135..30c0631 100644
--- a/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs
+++ b/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs
@@ -209,14 +209,7 @@ public virtual void Dispose()
         {
             foreach (string directory in directories)
             {
-                try
-                {
-                    DirectoryHelper.DeleteDirectory(directory);
-                }
-                catch (UnauthorizedAccessException ex)
-                {
-                    Trace.WriteLine(GetErrorMessage(directory, ex));
-                }
+                DirectoryHelper.DeleteDirectory(directory);
             }

 #if LEAKS_IDENTIFYING
@@ -232,17 +225,6 @@ public virtual void Dispose()
 #endif
         }

-        private static string GetErrorMessage(string path, Exception ex)
-        {
-            return string.Format("{0}The directory '{1}' could not be deleted due to a {2}: {3}" +
-                                 "{0}Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files." +
-                                 "{0}Known and common causes include:" +
-                                 "{0}- Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)" +
-                                 "{0}- Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus)" +
-                                 "{0}- TortoiseGit (change the 'Icon Overlays' settings, e.g., adding the bin folder of LibGit2Sharp.Tests to 'Exclude paths' and appending an '*' to exclude all subfolders as well)",
-                Environment.NewLine, Path.GetFullPath(path), ex.GetType(), ex.Message);
-        }
-
         protected static void InconclusiveIf(Func<bool> predicate, string message)
         {
             if (!predicate())
diff --git a/LibGit2Sharp.Tests/TestHelpers/DirectoryHelper.cs b/LibGit2Sharp.Tests/TestHelpers/DirectoryHelper.cs
index 25a7d85..094096e 100644
--- a/LibGit2Sharp.Tests/TestHelpers/DirectoryHelper.cs
+++ b/LibGit2Sharp.Tests/TestHelpers/DirectoryHelper.cs
@@ -2,14 +2,13 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
+using System.Linq;
+using System.Threading;

 namespace LibGit2Sharp.Tests.TestHelpers
 {
     public static class DirectoryHelper
     {
-        // Set this to true to have DirectoryHelper.Delete() write verbose trace messages.
-        private static bool verbose = true;
-
         private static readonly Dictionary<string, string> toRename = new Dictionary<string, string>
         {
             { "dot_git", ".git" },
@@ -50,58 +49,71 @@ public static void DeleteDirectory(string directoryPath)

             if (!Directory.Exists(directoryPath))
             {
-                Trace.WriteLineIf(verbose, string.Format("Directory '{0}' is missing and can't be removed.", directoryPath));
+                Trace.WriteLine(string.Format("Directory '{0}' is missing and can't be removed.", directoryPath));
                 return;
             }

+            DeleteDirectory_Internal(directoryPath);
+        }
+
+        private static void DeleteDirectory_Internal(string directoryPath)
+        {
+            NormalizeAttributes(directoryPath);
+
+            TryRecurseDelete(directoryPath, 5, 16, 2);
+        }
+
+        private static void NormalizeAttributes(string directoryPath)
+        {
             string[] files = Directory.GetFiles(directoryPath);
             string[] dirs = Directory.GetDirectories(directoryPath);

             foreach (string file in files)
             {
-                // We only set the file's attributes at this point and later delete its parent
-                // directory recursively, at which time we will also deal with exceptions.
                 File.SetAttributes(file, FileAttributes.Normal);
             }

             foreach (string dir in dirs)
             {
-                DeleteDirectory(dir);
+                NormalizeAttributes(dir);
             }

             File.SetAttributes(directoryPath, FileAttributes.Normal);
-            try
-            {
-                Directory.Delete(directoryPath, true);
-            }
-            catch (IOException ex1)
-            {
-                // Retry if the directory was allegedly not found or not empty.
-                Trace.WriteLineIf(verbose, "Retrying to delete '" + directoryPath + "' after catching " + ex1.GetType());
-                Directory.Delete(directoryPath, true);
-            }
-            catch (UnauthorizedAccessException ex1)
+        }
+
+        private static void TryRecurseDelete(
+            string directoryPath, 
+            int numberOfAttempts, int initialThreadSleepDuration, int durationMultiplier)
+        {
+            var whitelist = new[] { typeof (IOException), typeof (UnauthorizedAccessException) };
+
+            for (var i=1; i <= numberOfAttempts; i++)
             {
                 try
                 {
-                    // Retry if there was an access problem. But wait a little while before trying again,
-                    // because the issue might no longer exist then. Sleep(0) did not help, so using 100.
-                    System.Threading.Thread.Sleep(100);
-
-                    Trace.WriteLineIf(verbose, "Retrying to delete '" + directoryPath + "' after catching " + ex1.GetType());
                     Directory.Delete(directoryPath, true);
                 }
-                catch (IOException ex2)
+                catch (Exception e)
                 {
-                    // Retry if the directory was allegedly not found or not empty.
-                    Trace.WriteLineIf(verbose, "Re trying to delete '" + directoryPath + "' after catching " + ex2.GetType());
-                    Directory.Delete(directoryPath, true);
-                }
-                catch (UnauthorizedAccessException ex2)
-                {
-                    // Don't attempt another deletion if this exception is caught a second time.
-                    Trace.WriteLineIf(verbose, "Not trying to delete '" + directoryPath + "' again after catching " + ex2.GetType() + " a second time");
-                    throw;
+                    if (!whitelist.Contains(e.GetType()))
+                    {
+                        throw;
+                    }
+
+                    if (i != numberOfAttempts)
+                    {
+                        var sleepDuration = initialThreadSleepDuration * Math.Pow(durationMultiplier, (i -1));
+                        Thread.Sleep((int)sleepDuration);
+                        continue;
+                    }
+
+                    Trace.WriteLine(string.Format("{0}The directory '{1}' could not be deleted ({2} attempts were made) due to a {3}: {4}" +
+                                                  "{0}Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files." +
+                                                  "{0}Known and common causes include:" +
+                                                  "{0}- Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)" +
+                                                  "{0}- Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus)" +
+                                                  "{0}- TortoiseGit (change the 'Icon Overlays' settings, e.g., adding the bin folder of LibGit2Sharp.Tests to 'Exclude paths' and appending an '*' to exclude all subfolders as well)",
+                        Environment.NewLine, Path.GetFullPath(directoryPath), numberOfAttempts, e.GetType(), e.Message));
                 }
             }
         }

Thoughts?

@ThomasBarnekow
Copy link
Contributor Author

@nulltoken That is certainly a more flexible approach. Did you test it with TortoiseGit's TGitCache background process trying to do its thing on the test repos? Did it remove all directories?

I can change my stuff to reflect your proposal. But I think this could be done a bit more elegantly, like so:

private static Type[] whitelist = new[] { typeof(IOException), typeof(UnauthorizedAccessException) };

private static void TryRecurseDelete(string directoryPath, int attempt, int maxAttempts, int millisecondsTimeout, int timeoutFactor)
{
    try
    {
        Directory.Delete(directoryPath, true);
    }
    catch (Exception ex)
    {
        if (!whitelist.Contains(ex.GetType()))
        {
            throw;
        }
        if (attempt < maxAttempts)
        {
            Thread.Sleep(millisecondsTimeout);
            TryRecurseDelete(directoryPath, attempt + 1, maxAttempts, millisecondsTimeout * timeoutFactor, timeoutFactor);
        }
        else
        {
            Trace.WriteLine(string.Format("{0}The directory '{1}' could not be deleted due to a {2}: {3}" +
                                            "{0}Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files." +
                                            "{0}Known and common causes include:" +
                                            "{0}- Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)" +
                                            "{0}- Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus)" +
                                            "{0}- TortoiseGit (change the 'Icon Overlays' settings, e.g., adding the bin folder of LibGit2Sharp.Tests to 'Exclude paths' and appending an '*' to exclude all subfolders as well)",
                Environment.NewLine, Path.GetFullPath(directoryPath), ex.GetType(), ex.Message));
        }
    }
}

This is not tested, it just compiles.

@nulltoken
Copy link
Member

I can change my stuff to reflect your proposal. But I think this could be done a bit more elegantly.

Recursing from within a catch block makes me feels a bit uneasy. I'd rather stick with a version wrapping the try/catch statement.

Did you test it with TortoiseGit's TGitCache background process trying to do its thing on the test repos? Did it remove all directories?

Unfortunately, I don't use TortoiseGit. I've done some tests keeping a hold on some files/folders to ensure that the code was behaving as expected, but I'd very grateful if you could check this against your own environment.

@ThomasBarnekow
Copy link
Contributor Author

@nulltoken Have a look. This is essentially what you proposed with two fixes and minor modifications.

The DirectoryHelper.DeleteDirectory() method throws exceptions.
Affected tests will be reported as FAILED although this happens
during cleanup after the test was run.

This commit deals with those exceptions by retrying to delete
the directory up to four times, which removes directories in all
but rare cases. The method catches DirectoryNotFoundException,
IOException, and UnauthorizedAccessException and will only log a
trace message if it can't finally delete the directory. It will
rethrow any other type of exception.

Closes libgit2#970.
try
{
Directory.Delete(directoryPath, true);
return;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

That return statement was missing in your proposal. However, we don't want to continue after a successful attempt.

Copy link
Member

Choose a reason for hiding this comment

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

Indeed! Good catch!

@nulltoken nulltoken added this to the v0.22 milestone Feb 23, 2015
nulltoken added a commit that referenced this pull request Feb 23, 2015
@nulltoken nulltoken merged commit 8aa53b6 into libgit2:vNext Feb 23, 2015
@nulltoken
Copy link
Member

👍

Cheers!

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.

4 participants
0