[Python-ideas] Implementation of shutil.move
Mike Graham
mikegraham at gmail.com
Fri Aug 12 15:20:50 CEST 2011
On Fri, Aug 12, 2011 at 7:59 AM, David Townshend <aquavitae69 at gmail.com> wrote:
> The shutil.move function uses os.rename to move files on the same file
> system. On unix, this function will overwrite an existing destination, so
> the obvious approach is
> if not os.path.exists(dst):
> shutil.move(src, dst)
> But this could result in race conditions if dst is created after
> os.path.exists and before shutil.move. From my research, it seems that this
> is a limitation in the unix c library, but it should be possible to avoid it
> through a workaround (pieced together
> from http://bytes.com/topic/python/answers/555794-safely-renaming-file-without-overwriting).
> This involves some fairly low-level work, so I propose adding a new move2
> function to shutil, which raises an error if dst exists and locking it if it
> doesn't:
> def move2(src, dst):
> try:
> fd = os.open(dst, os.O_EXCL | os.O_CREAT)
> except OSError:
> raise Error('Destination exists')
> try:
> move(src, dst)
> finally:
> os.close(fd)
> This could be optimised by using shutil.move code rather than just calling
> it, but the idea is that an attempt is made to create dst with exclusive
> access. If this fails, then it means that the file exists, but if it passes,
> then dst is locked so no other process can create it.
This type of problem comes up regularly and a lot of user code is
riddled with this kind of race conditions. Many (most?) are avoidable
now by writing code to using EAFP rather than LBYL, but many are not.
I wonder if this broader problem could be addressed by a context
manager.
Something to the general effect of the usage
try:
with lockfile(dst):
move(src, dst)
except OSError as e:
if e != errno.EEXIST:
raise
raise AppSpecificError("File already exists.") # or whatever
and a definition like
@contextlib.contextmanager
def lockfile(path):
fd = os.open(path, os.O_EXCL | os.O_CREAT)
yield
os.close(fd)
The usage is still sort of ugly, but I'm not sure I can think of a
general way that isn't.
Mike
More information about the Python-ideas
mailing list