I'm using .NET 3.5, trying to recursively delete a directory using:
Directory.Delete(myPath, true);
My understanding is that this should throw if files are in use or there is a permissions problem, but otherwise it should delete the directory and all of its contents.
However, I occasionally get this:
System.IO.IOException: The directory is not empty.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.Directory.DeleteHelper(String fullPath, String userPath, Boolean recursive)
at System.IO.Directory.Delete(String fullPath, String userPath, Boolean recursive)
...
I'm not surprised that the method sometimes throws, but I'm surprised to get this particular message when recursive is true. (I know the directory is not empty.)
Is there a reason I'd see this instead of AccessViolationException?
I'm surprised that no one thought of this simple non-recursive method, which can delete directories containing read only files, without needing to change read only attribute of each of them.
(Change a bit to not to fire a cmd window momentarily, which is available all over the internet)
in case of network files, Directory.DeleteHelper(recursive:=true) might cause IOException which caused by the delay of deleting file
Is it possible you have a race condition where another thread or process is adding files to the directory:
The sequence would be:
Deleter process A:
If someone else adds a file between 1 & 2, then maybe 2 would throw the exception listed?
Before going further, check for the following reasons that are under your control:
Otherwise, check for the following legitimate reasons outside of your control:
If any of the above is the problem, you should understand why it happens before trying to improve your deletion code. Should your app be deleting read-only or inaccessible files? Who marked them that way, and why?
Once you have ruled out the above reasons, there's still a possibility of spurious failures. The deletion will fail if anyone holds a handle to any of the files or folders being deleted, and there are many reasons why someone may be enumerating the folder or reading its files:
The general approach to deal with spurious failures is to try multiple times, pausing between the attempts. You obviously don't want to keep trying forever, so you should give up after a certain number of attempts and either throw an exception or ignore the error. Like this:
In my opinion, a helper like that should be used for all deletions because spurious failures are always possible. However, YOU SHOULD ADAPT THIS CODE TO YOUR USE CASE, not just blindly copy it.
I had spurious failures for an internal data folder generated by my app, located under %LocalAppData%, so my analysis goes like this:
The folder is controlled solely by my application, and the user has no valid reason to go and mark things as read-only or inaccessible inside that folder, so I don't try to handle that case.
There's no valuable user-created stuff in there, so there's no risk of forcefully deleting something by mistake.
Being an internal data folder, I don't expect it to be open in explorer, at least I don't feel the need to specifically handle the case (i.e. I'm fine handling that case via support).
If all attempts fail, I choose to ignore the error. Worst case, the app fails to unpack some newer resources, crashes and prompts the user to contact support, which is acceptable to me as long as it does not happen often. Or, if the app does not crash, it will leave some old data behind, which again is acceptable to me.
I choose to limit retries to 500ms (50 * 10). This is an arbitrary threshold which works in practice; I wanted the threshold to be short enough so that users wouldn't kill the app, thinking that it has stopped responding. On the other hand, half a second is plenty of time for the offender to finish processing my folder. Judging from other SO answers which sometimes find even
Sleep(0)
to be acceptable, very few users will ever experience more than a single retry.I retry every 50ms, which is another arbitrary number. I feel that if a file is being processed (indexed, checked) when I try to delete it, 50ms is about the right time to expect the processing to be completed in my case. Also, 50ms is small enough to not result in a noticeable slowdown; again,
Sleep(0)
seems to be enough in many cases, so we don't want to delay too much.The code retries on any IO exceptions. I don't normally expect any exceptions accessing %LocalAppData%, so I chose simplicity and accepted the risk of a 500ms delay in case a legitimate exception happens. I also didn't want to figure out a way to detect the exact exception that I want to retry on.
I've had this same problem with Windows Workflow Foundation on a build server with TFS2012. Internally, the workflow called Directory.Delete() with the recursive flag set to true. It appears to be network related in our case.
We were deleting a binary drop folder on a network share before re-creating and re-populating it with the latest binaries. Every other build would fail. When opening the drop folder after a failed build, the folder was empty, which indicates that every aspect of the Directory.Delete() call was successful except for deleting the actually directory.
The problem appears to be caused by the asynchronous nature of network file communications. The build server told the file server to delete all of the files and the file server reported that it had, even though it wasn't completely finished. Then the build server requested that the directory be deleted and the file server rejected the request because it hadn't completely finished deleting the files.
Two possible solutions in our case:
The latter method is quick and dirty but seems to do the trick.
I resolved one possible instance of the stated problem when methods were async and coded like this:
With this:
Conclusion? There is some asynchronous aspect of getting rid of the handle used to check existence that Microsoft has not been able to speak to. It's as if the asynchronous method inside an if statement has the if statement acting like a using statement.