I am working on server software that periodically needs to save data to disk. I need to make sure that the old file is overwritten, and that the file cannot get corrupted (e.g. only partially overwritten) in case of unexpected circumstances.
I've adopted the following pattern:
string tempFileName = Path.GetTempFileName(); // ...write out the data to temporary file... MoveOrReplaceFile(tempFileName, fileName);
...where MoveOrReplaceFile is:
public static void MoveOrReplaceFile( string source, string destination ) { if (source == null) throw new ArgumentNullException("source"); if (destination == null) throw new ArgumentNullException("destination"); if (File.Exists(destination)) { // File.Replace does not work across volumes if (Path.GetPathRoot(Path.GetFullPath(source)) == Path.GetPathRoot(Path.GetFullPath(destination))) { File.Replace(source, destination, null, true); } else { File.Copy(source, destination, true); } } else { File.Move(source, destination); } }
This works well as long as the server has exclusive access to files. However, File.Replace appears to be very sensitive to external access to files. Any time my software runs on a system with an antivirus or a real-time backup system, random File.Replace errors start popping up:
System.IO.IOException: Unable to remove the file to be replaced.
Here are some possible causes that I've eliminated:
And here are some suggestions that I've come across, and why I'd rather not use them:
I'd appreciate any input on either getting File.Replace to work every time or, more generally, saving/overwriting files on disk reliably.
In computing, replace is a command that is used to replace one or more existing computer files or add new files to a target directory. Files with a hidden or system attribute set cannot be replaced using replace . The command lists all files that are replaced.
replace() is atomic on ALL platforms under most conditions.
You really want to use the 3rd parameter, the backup file name. That allows Windows to simply rename the original file without having to delete it. Deleting will fail if any other process has the file opened without delete sharing, renaming is never a problem. You could then delete it yourself after the Replace() call and ignore an error. Also delete it before the Replace() call so the rename won't fail and you'll cleanup failed earlier attempts. So roughly:
string backup = destination + ".bak"; File.Delete(backup); File.Replace(source, destination, backup, true); try { File.Delete(backup); } catch { // optional: filesToDeleteLater.Add(backup); }
There are several possible approaches, here some of them:
Of course all 3 approaches have their own drawbacks and advantages
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With