3

While testing some things out about writing/creating/deleting of files i made the following program that deletes and creates a file in a loop n times.

static string path = @"C:\Users\Remy\Desktop\Testing";
static readonly int sampleSize = 10000; // Amount of iterations the methods will be run for.

static byte[] sourceFile;

static void Main(string[] args)
{
    using (FileStream fs = new FileStream(path + @"\SourceFile.txt", FileMode.Open, FileAccess.Read))
    {
        sourceFile = new byte[fs.Length];
        fs.Read(sourceFile, 0, sourceFile.Length);
    }

    string filePath = path + @"\Destination.txt";
    for (int i = 0; i < sampleSize; i++)
    {
        if (File.Exists(filePath))
        {
            File.SetAttributes(filePath, FileAttributes.Normal);
            File.Delete(filePath);//Error sometimes throws here.
        }

        using (FileStream file = File.OpenWrite(filePath))
        {
            file.Write(sourceFile, 0, sourceFile.Length);
        }
    }

    Console.ReadLine();
}

This program works most of the time as expected when the amount of iterations isn't too high (about 1000). It will delete the old file and create a new one.

However when I increase the amount of iterations to 10000/100000 issues arise where on rare occassion (about 0.03% of the times) it throws System.UnauthorizedAccessException at using (FileStream file = File.OpenWrite(filePath)), while succesfully passing the other 99.97% of the time. When the error throws the file doens't get created.

This happens both in VS (as admin) using Debug/release, and on the build .exe run as administrator.

When looking around this issue i found the following answers regarding Unauth... exceptions.

  • this answer suggests setting the Attributes, but as seen in my example I already do that.
  • this and some other answers suggest running the application with admin rights. Which i'm already doing aswel.

I also added the permissions of the parent folder to allow full control to Everyone on all files and subfolders.

At first I thought maybe the file I was creating wasn't big enough (currently writing 976kb of random data) and for some reason the program iterated over creation/deletion faster than the OS/harddisk could handle. But the same behaviour occurs when increasing filesize

I've tested it across 3 machines, and it happened on them all.

Can this be a case of windows throwing an exception because of a false positive? Seeing that it only happens on big itterations? Am I missing something completely different here?

Note: I'm not looking for a way to handle the the exception. I can handle that. What I'm looking for a reason why this odd behaviour happens, and if possible to prevent it instead of curing it


Environment

Disk i'm writing to is a Crucial MX300 SSD, using sata 3 without a RAID. 16 GB ram. OS Windows 10 (pro) 64-bit. The system is running as idle as possible while running the program.

The Console application is targeting .NET Framework 4.6.1 build using Visual studio 2017 with Release Any CPU setting.


Additional things i've tried as per comment suggestions:

I tried adding a Thread.Sleep after creation and deletion to make sure Windows gets to clear the file cache. This still throws the exception but this time it throws the exception on File.Delete(filePath); instead.

Turning off Windows Defender etc also yields the result of the error being thrown on File.Delete(filePath) instead of using(FIleStream....) aswel.

Write to file using the following instead:

using (FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.None)) 
{ 
    file.Write(sourceFile, 0, sourceFile.Length); file.Flush(flushToDisk: true); 
}

also yields the same Exception being thrown

Community
  • 1
  • 1
Remy
  • 4,843
  • 5
  • 30
  • 60
  • Are files being created inside the directories in between? – EzLo Feb 05 '19 at 08:18
  • My best guess is that windows caches the writes to the file table and at some point just can't keep up with your rapid deleting and creating. Does the problem also occur with a small delay, say 50ms in between deleting and writing/setting attributes? Thread.Sleep(50) – FalcoGer Feb 05 '19 at 08:35
  • Does the error continue to pop up after the first time? (when you catch it and just continue the loop) or does it only pop up once and then again after another x iterations? also `string filePath = ...` should be outside the loop, put an extra scope if you want it local. – FalcoGer Feb 05 '19 at 08:47
  • 1
    @remy_rm Do you have any antivirus software including Windows Defender, Microsoft Essentials etc, installed on your PC? – Woldemar89 Feb 05 '19 at 09:01
  • @remy_rm, what is your ? I think you should use folder not inside your solution folder - something like independent "D:\folder". If your path placed inside solution folder, it may be controlled by VS and\or Git that can check newly added files. Also try to run app in Release config and\or not under VS. – Woldemar89 Feb 05 '19 at 09:19
  • I have run your sample with sampleSize=50k, 100k, 1294KB file with random data on my PC under VS 2017 Community in Debug AnyCPU(prefer 32bit) configuration, .NET 4.7.1, console app was added to local Git repo, run without Administrator permissions (but current user is administrator), also I have m2 SSD and Win10 x64 Pro. It works well without any exceptions. – Woldemar89 Feb 05 '19 at 09:34
  • @remy_rm try also this: `using (FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.None)) { file.Write(sourceFile, 0, sourceFile.Length); file.Flush(flushToDisk: true); }` – Woldemar89 Feb 05 '19 at 09:46
  • Then try to monitor I\O folder activity to determine that other apps use file: https://www.thewindowsclub.com/thefolderspy-lets-you-track-all-activities-in-any-folder https://superuser.com/questions/77351/tracking-disk-i-o-by-application-in-windows, http://support.veryant.com/support/phpkb/question.php?ID=136, – Woldemar89 Feb 05 '19 at 10:09
  • If it was to be locked due to another file using the file it should throw a `System.IO.IOException The process cannot access the file because it is being used by another process` exception though, and not a permission exception. – Remy Feb 05 '19 at 10:25
  • @remy_rm what type of VS project you created? Console App .NET Framework 4.6.1? – Woldemar89 Feb 05 '19 at 10:38
  • Yes, Console app (.NET framework) targeting 4.6.1 – Remy Feb 05 '19 at 10:45
  • Remy, I have same issue. File system busy certainly the cause. Did you ever find a way to determine if the delete completed? – Leftware Nov 09 '20 at 15:40
  • @Leftware I have not unfortunately – Remy Nov 09 '20 at 17:54

1 Answers1

1

There is no way you can get rid of this kind of issues, only way to handle is to prevent or avoid and make some logs if operation fails. Try operation like this multiple times if fails. Previously I have created one, here is code snippet.

public IList<string> ReadLineByLineFromFile( string filePath )
{
    const int numberOfRetries = 3;
    const int delayOnRetry = 500;

    bool success = false;
    List<string> logs = null;

    for ( int i = 0; i <= numberOfRetries && success == false; i++ )
    {
        try
        {
            logs = new List<string>();

            const Int32 bufferSize = 128;
            using ( var fileStream = File.Open( filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite ) )
            using ( var streamReader = new StreamReader( fileStream, Encoding.UTF8, true, bufferSize ) )
            {
                string line;

                while ( ( line = streamReader.ReadLine() ) != null )
                {
                    logs.Add( line );
                }
            }

            success = true;
        }
        catch ( IOException ex ) when ( i < numberOfRetries )
        {
            Local.Instance().Logger.Warn( ex, "Retrying reading logs from file path {0}, retry count {1} with dealy {2} ms.", filePath, i + 1,
                delayOnRetry );

            System.Threading.Thread.Sleep( delayOnRetry );
        }
    }

    GC.Collect();

    return logs;
}
Ankush Madankar
  • 3,689
  • 4
  • 40
  • 74
  • Though it does remedy the issue well it doesn't prevent it unfortunately, which is what I am trying to find out. Thanks for the snippet still. – Remy Feb 05 '19 at 09:28
  • I'd do the same to be honest, but I'm still curious why it fails. – FalcoGer Feb 05 '19 at 09:33
  • @FalcoGer @remy_rm Since any file can be accessible from outside from scope of this code and we don't have control over it. Make use of `.dat` file if you want file only accessible for your application. – Ankush Madankar Feb 05 '19 at 09:41