Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

System.IO.File.Delete() / System.IO.File.Move() sometimes does not work

A Winforms program needs to save some run time information to an XML file. The file can sometimes be a couple of hundred kilobytes in size. During beta testing we found some users would not hesitate to terminate processes seemingly at random and occasionally causing the file to be half written and therefore corrupted.

As such, we changed the algorithm to save to a temp file and then to delete the real file and do a move.

Our code currently looks like this..

private void Save()
{
    XmlTextWriter streamWriter = null;
    try
    {
        streamWriter = new XmlTextWriter(xmlTempFilePath, System.Text.Encoding.UTF8);

        XmlSerializer xmlSerializer = new XmlSerializer(typeof(MyCollection));

        xmlSerializer.Serialize(streamWriter, myCollection);

        if (streamWriter != null)
            streamWriter.Close();

        // Delete the original file
        System.IO.File.Delete(xmlFilePath);

        // Do a move over the top of the original file 
        System.IO.File.Move(xmlTempFilePath, xmlFilePath);
    }
    catch (System.Exception ex)
    {
        throw new InvalidOperationException("Could not save the xml file.", ex);
    }
    finally
    {
        if (streamWriter != null)
            streamWriter.Close();
    }
}

This works in the lab and in production almost all of the time. The program is running on 12 computers and this code is called on average once every 5 min. About once or twice a day we get this exception:

System.InvalidOperationException: 
Could not save the xml file. 
---> System.IO.IOException: Cannot create a file when that file already exists.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.__Error.WinIOError()
at System.IO.File.Move(String sourceFileName, String destFileName)
at MyApp.MyNamespace.InternalSave()

It is as if the Delete is not actually issued to the hard drive before the Move is issued.

This is happening on Win7 machines.

A couple of questions: Is there some concept of a Flush() one can do for the entire disk operating system? Is this a bug with my code, .net, the OS or something else? Should I be putting in some Thread.Sleep(x)? Maybe I should do a File.Copy(src, dest, true)? Should I write the following code? (But it looks pretty silly.)

while (System.IO.File.Exists(xmlFilePath))
{
    System.IO.File.Delete(xmlFilePath);
}

// Do a move over the top of the main file 
bool done = false;
while (!done)
{
    try
    {
        System.IO.File.Move(xmlTempFilePath, xmlFilePath);
        done = true;
    }
    catch (System.IO.IOException)
    {
        // let it loop
    }
}

Has anyone seen this before?

like image 468
DJA Avatar asked Jun 24 '11 11:06

DJA


People also ask

Does file move delete?

Moving – move the original files or folder from one place to another (change the destination). The move deletes the original file or folder, while copy creates a duplicate.

Does file move overwrite?

Move(String, String, Boolean) Moves a specified file to a new location, providing the options to specify a new file name and to overwrite the destination file if it already exists.

How do I move a file in C#?

Move() is an inbuilt File class method that is used to move a specified file to a new location. This method also provides the option to specify a new file name. Syntax: public static void Move (string sourceFileName, string destFileName);

Is file move an atomic?

net, File. Move() will fallback to a non-atomic operation if the operation cannot be done atomically but it is generally safe to assume that renaming a file within a directory will always use the atomic codepath).


2 Answers

You can never assume that you can delete a file and remove it on a multi-user multi-tasking operating system. Short from another app or the user herself having an interest in the file, you've also got services running that are interested in files. A virus scanner and a search indexer are classic trouble makers.

Such programs open a file and try to minimize the impact that has by specifying delete share access. That's available in .NET as well, it is the FileShare.Delete option. With that option in place, Windows allows a process to delete the file, even though it is opened. It gets internally marked as "delete pending". The file does not actually get removed from the file system, it is still there after the File.Delete call. Anybody that tries to open the file after that gets an access denied error. The file doesn't actually get removed until the last handle to the file object gets closed.

You can probably see where this is heading, this explains why File.Delete succeeded but File.Move failed. What you need to do is File.Move the file first so it has a different name. Then rename the new file, then delete the original. Very first thing you do is delete a possible stray copy with the renamed name, it might have been left behind by a power failure.

Summarizing:

  1. Create file.new
  2. Delete file.tmp
  3. Rename file.xml to file.tmp
  4. Rename file.new to file.xml
  5. Delete file.tmp

Failure of step 5 is not critical.

like image 73
Hans Passant Avatar answered Sep 28 '22 05:09

Hans Passant


how about using Move.Copy with overwrite set to true so that it overwrites your app state and then you can delete your temp state ?

You can also attach to App_Exit event and try to perform clean shut down?

like image 27
Bek Raupov Avatar answered Sep 28 '22 04:09

Bek Raupov