Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to delete a locked (flock) file without race condition: before or after releasing the lock?

I am implementing a mutual exclusion mechanism based on a file lock. Other instances of my script know that they are not supposed to run when they come accross a specific file, which is locked.

In order to achieve this, I have created and locked the file using fcntl.flock. When I release the lock I also want to clean up the file, that it doesn't sit there indicating an old pid when no process is actually running.

My question is, when and how should I clean up the file, especially at what point can I delete it. Basically I see two options:

  • truncate and delete the file before the lock is released
  • truncate and delete the file after the lock is released

From my understanding, each one exposes my application to slightly different race conditions. What is best practice, what have I missed?

Here's an (overly simplified) Example:

import fcntl
import os
import sys
import time

# open file for read/write, create if necessary
with open('my_lock_file.pid', 'a+') as f:
    # acquire lock, raises exception if lock is hold by another process
    try:
        fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
    except IOError:
        print 'other process running:', f.readline()
        sys.exit()

    try:
        # do something
        f.write('%d\n' % os.getpid())
        f.flush()
        # more stuff here ...
        time.sleep(5)
    finally:
        # clean up here?
        # release lock
        fcntl.flock(f, fcntl.LOCK_UN)
        # clean up here?
# clean up here?
like image 702
moooeeeep Avatar asked Oct 16 '15 09:10

moooeeeep


People also ask

What does it mean when files are locked?

File locking is a feature that prevents a file from being edited.


1 Answers

I found this related question to give some suggestions about how to handle this case:

  • flock(): removing locked file without race condition?

It also made me aware of another possible race condition, which occurs when another process deletes the file just after it's been opened by the current process. This would cause the current process to lock a file that is no longer to be found on the filesystem and thus fail to block the next process that would create it anew.

There I found the suggestion to make use of the open flag O_EXCL for atomic exclusive file creation, which is exposed via the os.open() function for low-level file operations. I then implemented the following example accordingly:

import os
import sys
import time

# acquire: open file for write, create if possible, exclusive access guaranteed
fname = 'my_lock_file.pid'
try:
    fd = os.open(fname, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
except OSError:
    # failed to open, another process is running
    with open(fname) as f:
        print "other process running:", f.readline()
        sys.exit()

try:
    os.write(fd, '%d\n' % os.getpid())
    os.fsync(fd)
    # do something
    time.sleep(5)
finally:
    os.close(fd)
    # release: delete file
    os.remove(fname)

After implementing this, I found out that this is exactly the same approach the lockfile module uses for its pid files.

For reference:

  • http://man7.org/linux/man-pages/man2/open.2.html
  • https://docs.python.org/2/library/os.html#os.open
  • https://docs.python.org/2/library/os.html#open-constants
  • https://pypi.python.org/pypi/lockfile
  • http://git.openstack.org/cgit/openstack/pylockfile/tree/lockfile/pidlockfile.py
like image 57
moooeeeep Avatar answered Oct 05 '22 07:10

moooeeeep