0

I have a c# console app that frequently and quickly writes maybe 1-2mb worth of data to a file (keeps overwriting same file). I then run the program in an infinite loop like this

@echo off
:while
(
   C:\sync.exe
   goto :while
)

and then the c# writes text like this

    private static async Task WriteFile(string filePath, string text)
    {
        string base_path = AppDomain.CurrentDomain.BaseDirectory;
        string file_path = Path.Combine(base_path, filePath);
        using (StreamWriter outputFile = new StreamWriter(file_path))
        {
            await outputFile.WriteAsync(text);
        }
    }

But what I've noticed is that when I end the program, with ctrl+c, or stop the debugger, while it's in the middle of writing, it stops the writing and leave the file corrupted and with missing data.

Is there a way that I can ensure that if the program is stopped mid-write, it either undoes the change (it overwrites the previous file that was there) leaving the old one back again, or it somehow finishes writing (which will take less than a second)?

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
omega
  • 40,311
  • 81
  • 251
  • 474
  • When a process is killed, you can do nothing to prevent that, as I know. Like if you power off the compurer. –  Nov 13 '19 at 02:28
  • 2
    No. Not different from the machine losing power. The workaround is a very simple one, write to a temporary file and rename it when you're done. The rename is atomic. – Hans Passant Nov 13 '19 at 02:28
  • yeah but what if the program ends somewhere in between deleting old file and renaming new file? – omega Nov 13 '19 at 02:30
  • c'mon, we need to think out of the box solutions – omega Nov 13 '19 at 02:32
  • Are you in a position to modify the reading program? – ProgrammingLlama Nov 13 '19 at 02:33
  • yes I can modify it. – omega Nov 13 '19 at 02:34
  • Directly supported in .NET with FileInfo.Replace(). – Hans Passant Nov 13 '19 at 02:34
  • Also @HansPassant modern computers have power bricks, so that when power is shut off, they still have power in the brick to keep it going a little longer. Thats the kind of solution i need. – omega Nov 13 '19 at 02:35
  • What if I don't make it async, and keep it sync, so it's an atomic operation? – omega Nov 13 '19 at 02:37
  • 1
    You are ignoring good advice, hard to help you. A UPS does nothing to prevent a program from getting terminated unexpectedly of course. – Hans Passant Nov 13 '19 at 02:37
  • also can you show example of how FileInfo.Replace() is used? – omega Nov 13 '19 at 02:39
  • I think question is overly broad as it requests solutions outside of existing approaches to transactional storage... Since I'm @#$@ elitist I decided that duplicate is faster option to close rather than waiting for 5 "too broad" votes... I'd recommend retrying researching of existing solutions (like "c# transaction write file terminated") to narrow down why existing solutions do not work so question can be re-opened. – Alexei Levenkov Nov 13 '19 at 03:54

1 Answers1

2

Is there a way that I can ensure that if the program is stopped mid-write, it either undoes the change (it overwrites the previous file that was there) leaving the old one back again, or it somehow finishes writing (which will take less than a second)?

Yes... write to a different file. When the write is complete, copy the file using this prototype of the Copymethod, setting the third argument to true.

private static async Task WriteFile(string filePath, string text)
{
    string temp_file = File.GetTempFileName();
    string base_path = AppDomain.CurrentDomain.BaseDirectory;
    using (StreamWriter outputFile = new StreamWriter(temp_file))
    {
        await outputFile.WriteAsync(text);
    }
    string file_path = Path.Combine(base_path, filePath);
    File.Copy(temp_file, file_path, true);
    File.Delete(temp_file);
}
John Wu
  • 50,556
  • 8
  • 44
  • 80
  • but what if the program ends when doing the copy or delete? – omega Nov 13 '19 at 02:38
  • and is this better than using FileInfo.Replace() ? – omega Nov 13 '19 at 02:39
  • 1
    Nope, you can use File.Replace instead if you prefer. If you somehow interrupt your program in the middle of a single file operation (e.g. if you lose power), you are screwed of course. There's no way around that (that's why your computer says "Do not turn off your computer" sometimes-- even the gods at Microsoft can't work around that limitation). If this is a mission critical file, I suggest never overwriting it, and modifying whatever program that reads the file to search for the latest file that is available and intact instead. – John Wu Nov 13 '19 at 02:43
  • In what way is file.replace better than this? Also btw its `Path.GetTempFileName();` – omega Nov 13 '19 at 02:52
  • 1
    I am not sure this is true. NTFS supports transactions but for some reason Microsoft recommends to use another Win32 function for this use case called ReplaceFile - https://docs.microsoft.com/en-us/windows/win32/fileio/deprecation-of-txf#applications-updating-a-single-file-with-document-like-data If the replace method in .NET uses this function corruption is unlikely especially when the program is killed. It might be the case that only a pointer in the files table is updated atomically so even a power failure won't break this. I don't know if this is the case but it is better than copy. – Stilgar Nov 13 '19 at 02:56
  • How can I use the replaceFile method? – omega Nov 13 '19 at 02:59
  • Good comment @Stilgar, but **true atomicity** can't be achieved in practice, at least not in this universe. Every and any process takes a finite amount of time, if that process gets interupted then its internal state might get corrupted. However, the faster it is, the lesser the probability that an interruption occurs. – Javier Silva Ortíz Nov 13 '19 at 03:17
  • The best workaround is to write to a secondary file. And if the writing finishes, then literally update the file pointer in the NTFS table, but can we say for sure that `ReplaceFile` does this ? – Javier Silva Ortíz Nov 13 '19 at 03:20
  • I don't think you can take a directory entry for one file and point it at another-- that could cause huge issues (e.g. what do you do with the permissions?). The best you can hope for is to to delete or rename the old file and then rename the new one. So there is always a (small) window for stuff to end up in an inconsistent state. – John Wu Nov 13 '19 at 03:22