flock seems very unsafe, python fcntl bug?

Nick Craig-Wood nick at craig-wood.com
Mon Jun 23 06:32:06 EDT 2008


Jens Henrik Leonhard Jensen <jhlj at statsbiblioteket.dk> wrote:
>  Your problem is that open(...,'w') is not locked.
> 
>  Use something like:
>  lockf = open('aaa', 'a')
>  fnctl.flock(lockf,fnctl.LOCK_EX)
>  file = open('aaa', 'w')
>  file.write('asdf')
>  file.close()
>  lockf.close()

I've not seen that trick before - it is a good one.  It wouldn't work
for mandatory locking though...

The basic problem is getting the file open for read/write without
destroying the contents before you have the lock.  None of the
arguments to open() do the right thing....

Using an append open is a nice solution to that, apart from the fact
that you can only append data onto the file hence the other open.

Here are some other possible solutions and a test harness!  The low
level open is probably the most unixy solution.  If you could specify
os.O_RDWR|os.O_CREAT to the python open() function then it would be
the best, but none of the documented open strings do that

$ strace python -c 'open("aaa", "r+")' 2>&1 | grep 'open("aaa"'
open("aaa", O_RDWR|O_LARGEFILE)         = 3
$ strace python -c 'open("aaa", "w+")' 2>&1 | grep 'open("aaa"'
open("aaa", O_RDWR|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
$ strace python -c 'open("aaa", "wr+")' 2>&1 | grep 'open("aaa"'
open("aaa", O_RDWR|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 3
$ strace python -c 'open("aaa", "a+")' 2>&1 | grep 'open("aaa"'
open("aaa", O_RDWR|O_CREAT|O_APPEND|O_LARGEFILE, 0666) = 3


------------------------------------------------------------
"""
Testing writing stuff to a file with locking
"""

import os
import sys
import fcntl

def method_0(data):
    """
    Use an normal open

    This doesn't work because it truncates the data before it has the
    lock
    """
    fd = open('aaa', 'w')
    fcntl.flock(fd,fcntl.LOCK_EX)
    fd.write(data)
    fd.close()

def method_1(data):
    """
    Use an additional append open to lock
    """
    lock_fd = open('aaa', 'a')
    fcntl.flock(lock_fd,fcntl.LOCK_EX)
    fd = open('aaa', 'w')
    fd.write(data)
    fd.close()
    lock_fd.close()

def method_2(data):
    """
    Use a low level open
    """
    fd = os.fdopen(os.open('aaa', os.O_CREAT|os.O_RDWR), "r+")
    fcntl.flock(fd, fcntl.LOCK_EX)
    fd.truncate()
    fd.write(data)
    fd.close()

def method_3(data):
    """
    Using high level functions only

    Possibly racy on open when file doesn't exist
    """
    if not os.path.exists('aaa'):
        fd = open('aaa', 'w+')
    else:
        fd = open('aaa', 'r+')
    fcntl.flock(fd,fcntl.LOCK_EX)
    fd.truncate()
    fd.write(data)
    fd.close()

def check():
    fd = open('aaa', 'r')
    fcntl.flock(fd,fcntl.LOCK_EX)
    data = fd.read()
    fd.close()
    if data not in ("sausage", "potato"):
        raise AssertionError("Wrong data %r" % data)

if os.path.exists("aaa"):
    os.unlink("aaa")

if len(sys.argv) < 2:
    print "Syntax: %s <method number 0..3>" % sys.argv[0]
    raise SystemExit(1)
method = globals()["method_"+sys.argv[1]]
if os.fork():
    while 1:
        method("sausage")
        check()
else:
    while 1:
        method("potato")
        check()
------------------------------------------------------------


-- 
Nick Craig-Wood <nick at craig-wood.com> -- http://www.craig-wood.com/nick



More information about the Python-list mailing list