I'm opening a file with for reading that I had previously created in the user's %TEMP% folder, using the following code:
new FileStream(cacheFileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete);
On some user's computers, this sometimes throws an UnauthorizedAccessException with the message "Access to path ... is denied". I haven't been able to reproduce this. My initial guess is that an anti-virus or indexing engine is doing something funky, but I also noticed this code is using "FileShare.Delete", which I'm not sure should be there.
Is there a scenario where using "FileShare.Delete" leads to UnauthorizedAccessException ?
Yes, FileShare.Delete tends to cause this problem. Used by any program that runs in the background and scans files, file indexers and virus scanners are the common examples.
FileShare.Delete allows another process to delete the file, even though the background process still has the file opened and is reading from it. That other process will be oblivious that the file didn't actually disappear, for all it knows the file actually got deleted.
The trouble starts when that other process relies on the file actually being removed and does something else. Commonly triggered by creating a new file with the same name. Notably a very unwise way to save a file since it will cause complete data loss without a backup when the save fails, but this mistake is very common.
This will fail because the directory entry for the file is still present, it won't disappear until the last process that has the file opened closes the handle. Any other process that tries to open the file again will be slapped with error 5, "access denied". Including that process that deleted the file and tries to re-create it.
The workaround is to always use "transactional" saves, renaming the file before trying to overwrite it. Available in .NET with File.Replace(), in the native winapi with ReplaceFile(). Also easily done by hand, the workflow is:
- delete the backup file, stop if failed
- rename the old file to the backup filename, stop if failed
- write the new file using the original filename, rename backup back if failed
- delete the backup file, ignore failure
Step 2 ensures that there will never be any data loss, the original file stays intact if anything goes wrong. Step 4 ensures that FileShare.Delete will work as intended, that backup file will disappear eventually when other processes close their handles.
I found a scenario that reproduces this:
static void Main(string[] args)
{
string cacheFileName = @"C:\temp.txt";
using (var filestream = new FileStream(cacheFileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, 4096, FileOptions.SequentialScan))
{
filestream.Read(new byte[100], 1, 1);
Console.ReadLine();
GC.KeepAlive(filestream);
}
Console.WriteLine("Done!");
}
}
Create a "C:\temp.txt" file, then run this program. Try to delete the file with Explorer/TotalCommander, it won't complain but it won't delete the file either. Then, run the program again and it will throw the UnauthorizedAccessException. After you close both .exes, it looks like the file is finally deleted.
Removing "FileShare.Delete" solves this problem, as it simply won't let you try to delete the file while it's in use.