Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does finally ensure some code gets run atomically, no matter what?

Assume I'm going to write a Python script that catches the KeyboardInterrupt exception to be able to get terminated by the user using Ctrl+C safely

However, I can't put all critical actions (like file writes) into the catch block because it relies on local variables and to make sure a subsequent Ctrl+C does not break it anyway.

Would it work and be good practice to use a try-catch block with empty (pass) try part and all the code inside the finally part to define this snippet as "atomic, interrupt-safe code" which may not get interrupted mid-way?

Example:

try:
    with open("file.txt", "w") as f:
        for i in range(1000000):
            # imagine something useful that takes very long instead
            data = str(data ** (data ** data))
            try:
                pass
            finally:
                # ensure that this code is not interrupted to prevent file corruption:
                f.write(data)

except KeyboardInterrupt:
        print("User aborted, data created so far saved in file.txt")
        exit(0)

In this example I don't care for the currently produced data string, i.e. that creation could be interrupted and no write would be triggered. But once the write started, it must be completed, that's all I want to ensure. Also, what would happen if an exception (or KeyboardInterrupt) happened while performing the write inside the finally clause?

like image 507
Byte Commander Avatar asked Apr 21 '16 06:04

Byte Commander


2 Answers

Code in finally can still be interrupted too. Python makes no guarantees about this; all it guarantees is that execution will switch to the finally suite after the try suite completed or if an exception in the try suite was raised. A try can only handle exceptions raised within its scope, not outside of it, and finally is outside of that scope.

As such there is no point in using try on a pass statement. The pass is a no-op, it won't ever be interrupted, but the finally suite can easily be interrupted still.

You'll need to pick a different technique. You could write to a separate file and move that into place on successful completion; the OS guarantees that a file move is atomic, for example. Or record your last successful write position, and truncate the file to that point if a next write is interrupted. Or write markers in your file that signal a successful record, so that reads know what to ignore.

like image 117
Martijn Pieters Avatar answered Sep 30 '22 18:09

Martijn Pieters


In your case, there is no problem, because file writes are atomic, but if you have some file object implementetion, that is more complex, your try-except is in the wrong place. You have to place exception handling around the write:

try:
    f.write(data)
except:
    #do some action to restore file integrity
    raise

For example, if you write binary data, you could to the following:

filepos = f.tell()
try:
    f.write(data)
except:
    # remove the already written data
    f.seek(filepos)
    f.truncate()
    raise
like image 44
Daniel Avatar answered Sep 30 '22 19:09

Daniel