-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Atomic function to rename a file #53074
Comments
os.rename() is atomic on Linux, but on Windows it raises an error if the destination does already exist. Not atomic pseudo-code for Windows: Windows offers different functions depending on the version:
I don't think that it's possible to write an atomic rename (file) function for any OS, so it's only a "best effort" atomic function. The documentation will give a list of OS on which the operation *is* atomic (eg. Linux). Note: os.rename() uses MoveFileW() on Windows. |
A first implementation can be: if os.name in ('nt', 'ce'):
def atomic_rename(a, b):
if os.path.exists(b):
unlink(b)
rename(a, b)
else:
atomic_rename = os.rename This implementation is atomic on POSIX, and not atomic on Windows. Tell me if I am wrong. It can be improved later by adding the support of better Windows functions. |
See issue bpo-8604: proposal of a new "with atomic_write(filename) as fp: ..." context manager. |
Begin by removing the dest file is maybe not the safer approach :-) Here is a new try: begin by renaming the dest file to a new file. ------ # use maybe a PRNG instead of a dummy counter or tempfile
def _create_old_filename(filename):
old = filename + '.old'
index = 2
while os.path.exists(old):
old = filename + '-%s.old' % index
index += 1
return old
if os.name in ('nt', 'ce'):
def atomic_rename(src, dst):
if os.path.exists(dst):
old = _create_old_filename(dst)
rename(dst, old)
rename(src, dst)
unlink(old)
else:
rename(src, dst)
else:
atomic_rename = os.rename What can we do if "rename(src, dst)" fails? |
Wrong :-) "On how rename is broken in Mac OS X" "Update January 8, 2010: ... the original bug (5398777) was resolved in Snow Leopard but the issue I reported was not fixed. ..." |
We have to think about network file systems like NFS. Gnome (nautilus) had a bug on rename because NFS emitted a delete notification on a rename: It looks like rename is atomic, it's just a bug about notification. But other virtual file systems may not implement atomic rename (eg. is rename atomic with sshfs?). Should Python detect the file system type to choose the algorithm? I would like to say no, because I consider that as a file system (or kernel) bug, not a Python bug. -- Should we also implement a atomic version of shutil.move()? Support rename if the source and the destination are on different file systems. Or is shutil.move() already atomic? Note: this issue is only about renaming a *file*. Atomic rename of a directory is much more complex :-) |
It seems you are proposing to call "atomic" something which isn't atomic: def atomic_rename(src, dst):
if os.path.exists(dst):
old = _create_old_filename(dst)
rename(dst, old)
rename(src, dst)
unlink(old) |
@pitrou: Yes, as I wrote: it's not possible to write an atomic function for all OS. The documentation must give a list of the OS on which the function is atomic. Would you prefer to not define the function instead of writing a pseudo-atomic function? -- Java bug opened in 1996, closed with "Will Not Fix", last comment ("i am facing similar issue...") in 2008: "How to do atomic writes in a file" in a MSDN blog: Extract: "Sometimes shell operations like Delete, Rename can fail for various reasons. For example, it might just happen that an antivirus or content indexing application randomly scans the whole file system once in a while. So, potentially, the file Foo.Tmp.txt will be opened for a short period which will cause ... failed delete. And, not only that, but also Rename can fail if the old file already exists, and someone has an open handle on it." To avoid that "antivirus or content indexing application" open the file, we may need to use a lock on the files. |
Your current implementation is useless, since it doesn't achieve anything new. |
Someone may reimplement it with unlink+rename which is worse :-) But ok, you prefer to not define the function if no real atomic implementation |
Victor: you could always name it best_effort_at_atomic_rename :) |
Atomic file renames are reimplemented by:
and essential for many other projects out there, so this should be in standard library. Atomic renames depend on filesystem more that on the OS. |
Implementation of "atomic" rename() for Windows in Mercurial: |
Dulwich bug discussion (closed) - https://bugs.edge.launchpad.net/dulwich/+bug/557585 Trac implementation - http://trac.edgewall.org/browser/trunk/trac/util/__init__.py?#L82 Stackoverflow - http://stackoverflow.com/questions/167414/is-an-atomic-file-rename-with-overwrite-possible-on-windows |
About the function names:
Implement an atomic function to rename a directory is more complex and should be done in another issue. That's why I added "_file" suffix. |
[atomic_move_file-windows.py]: implementation of atomic_move_file() for Windows, depends on CreateTransaction() and MoveFileTransacted(), only available on Windows Vista, Windows Server 2008, or more recent version. This function *is* atomic. This function is also able to rename a directory! |
Does it work with FAT32 or network filesystem? |
According to the following article, a fsync is also needed on the directory after a rename. I don't understand if is it always needed for an atomic rename, or if we only need it for the "atomic write" pattern. http://lwn.net/Articles/457667/ "The more subtle usages deal with newly created files, or overwriting existing files. A newly created file may require an fsync() of not just the file itself, but also of the directory in which it was created (since this is where the file system looks to find your file). This behavior is actually file system (and mount option) dependent. You can either code specifically for each file system and mount option combination, or just perform fsync() calls on the directories to ensure that your code is portable." |
It's not needed if you just want atomicity, i.e. the file is visible either under its old name or its new name, but not neither or both. |
The recent issue bpo-13146 renewed my interest, so I'd like to make this move forward, since I think an atomic rename/write API could be quite useful.
But the problem is that rename is only atomic on POSIX, and not on Windows.
I'm -1 on exposing a "best effort" atomic rename/file API: either the OS offers the primitives necessary to achieve atomicity, or it doesn't. It's better to have a working implementation on some OSes than a flaky implementation on every OS. Note that I'll happily take over the atomic file API part (issue bpo-8604), but since my Windows kung-fu is so poor, it'd be nice if someone with some Windows experience could tackle this MoveFileTransacted |
MoveFileTransacted is only available under Vista or later. You should be able to use MoveFileEx for the same effect. |
"The solution? Let's remember that metadata changes are atomic. Rename is such a case." This is from a MSDN blog, I would hope he knows what he's talking about. (MoveFileEx appears in Windows XP, which is fine: in Python 3.3, "Windows 2000 and Windows platforms which set COMSPEC to command.com are no longer supported due to maintenance burden") |
Nice.
Hmmm. By the way:
There's exactly the same limitation with the POSIX version (except |
Standard rename (MoveFile) fails when the target exists, and that's
If you don't specify the MOVEFILE_COPY_ALLOWED flag, MoveFileEx also |
I see that Sun/Oracle Java trusts MoveFileEx to do atomic renames: 290 // atomic case (from http://www.docjar.com/html/api/sun/nio/fs/WindowsFileCopy.java.html ) |
So how about providing a new public |
What is the motivation for providing a new function? |
Because changing os.rename would break compatibility. |
Ah, I see, people may be depending on rename on Windows not overwriting. I suppose a new function (and eventually deprecating the old?) would be the most straightforward way forward, though I dislike the necessity :) An alternative might be a flag on rename: overwrite=['always', 'os_default'], with a warning and a switch of the default in a subsequent release. That's ugly too, of course. The existing per-platform variation in behavior of rename gives us a mess to deal with. |
I'd prefer an optional flag to rename() too. |
os.rename(overwrite=True) to produce consistent cross-platform behavior. |
How about overwrite=[None, True] with None meaning "OS default"?
I think we should be conservative with warnings and |
One of the Python advantages is providing predictable cross-platform behavior. If we can't introduce nice API without BC break, it is not a reason to introduce ulgy API. |
I'm good with None/True, but that would imply that for posix rename we'll need to implement the overwrite=False option...which would be a nice thing (the shell mv command has -i for that). I think a warning would be good, because a unix programmer will assume rename will work the same on windows as it does on posix, and vice versa for a windows programmer. I suppose the fact that we haven't gotten many (any?) complaints about it means it isn't that common a problem, though, so I don't feel strongly about it. |
My point was rather to forbid False as a value (on all OSes) :)
It is already documented. I don't think we want to add a warning for |
+1.
We cannot make rename() overwrite existing files by default (on Windows).
Why? if '-i' and os.path.exists(target_path):
continue
os.rename(src_path, target_path). But there's a TOCTTOU race. |
Ah, you are right about the race of course. So yes, I agree with your proposal. It's a weird API, but probably the best we can do. |
2011/12/23 Charles-François Natali <report@bugs.python.org>
I propose quite the opposite. rename() should not overwrite existing files |
|
2011/12/23 Charles-François Natali <report@bugs.python.org>
os.rename(overwrite=False) by default will do less harm than the opposite, Then I believe that having a small chance of overwriting file just created BUT let me remind you that this bug is about "Atomic rename" which should |
Disagreed.
Disagreed. |
On Fri, Dec 23, 2011 at 10:35 PM, Antoine Pitrou <report@bugs.python.org>wrote:
Fine. No arguments == no consensus.
As a Windows programmer I am quite surprised to read this thread with |
So maybe my warning idea isn't such a bad idea :) As a unix programmer, I was very surprised to read in this thread that Windows doesn't overwrite the file on rename. As a unix programmer, I don't check for errors on a rename, because I expect it to just work. I'd like the windows rename call to stop throwing errors if the file exists, it breaks my programs if they run on windows. (Actually, very few of my programs ever get run on Windows, but you get the idea.) Thus, the only possible course is to maintain backward compatibility, and allow the programmers who care to specify the desired behavior. Since one of the important aspects of 'rename' in unix programmers' minds is that it is atomic, a race condition is not acceptable, therefore overwrite=False is not an acceptable option for os.rename. |
As a Windows programmer, you are not really qualified to criticize the
We don't strive to achieve consensus. Agreement among the majority of |
Here is a patch adding os.replace(). |
New changeset 80ddbd822227 by Antoine Pitrou in branch 'default': |
os.replace() committed in 3.3! |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: