Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

fcntl.flock - how to implement a timeout?

I am using python 2.7

I want to create a wrapper function around fcntl.flock() that will timeout after a set interval:

wrapper_function(timeout):

I've tried calling on another thread and using thread.join(timeout) but it seems that fcntl.flock() continues blocking:

def GetLock(self, timeout):
    """Returns true if lock is aquired, false if lock is already in use"""
    self.__lock_file = open('proc_lock', 'w')

    def GetLockOrTimeOut():
        print 'ProcessLock: Acquiring Lock'            
        fcntl.flock(self.__lock_file.fileno(), fcntl.LOCK_EX)
        print 'ProcessLock: Lock Acquired'

    thread = threading.Thread(target=GetLockOrTimeOut)
    thread.start()
    thread.join(timeout)

    if thread.isAlive():
        print 'GetLock timed out'
        return False
    else:
        return True

I've looked into solutions for terminating threads, the most popular solution seems to be sub-classing threading.thread and adding a feature to raise an exception in the thread. However, I came across a link that says this method will not work with native calls, which I am pretty sure fcntl.flock() is calling a native function. Suggestions?

Context: I am using a file-lock to create a single instance application but I don't want a second instance of the application to sit around and hang until the first instance terminates.

like image 416
J Cooper Avatar asked Mar 10 '11 03:03

J Cooper


People also ask

What is fcntl flock in Linux?

fcntl. flock (fd, operation) ¶ Perform the lock operation operation on file descriptor fd (file objects providing a fileno () method are accepted as well). See the Unix manual flock (2) for details. (On some systems, this function is emulated using fcntl ().)

How does fcntl() work?

When fcntl () returns, the structure indicated by the flock pointer is changed to show the first lock that would prevent the proposed lock operation from taking place. The returned structure shows the type of lock that is set, the part of the file that is locked, and the job ID of the job that holds the lock. In the returned structure:

Why does fcntl() fail with the [einval] error?

If any other bits in the third argument are set, fcntl () fails with the [EINVAL] error. fcntl () returns 0 if it successfully sets the flags. Sets or clears a file segment lock. You must specify a third argument of type struct flock *. See File Locking for details. fcntl () returns 0 if it successfully clears the lock.

Why does fcntl() return 0 when F_setcvt returns 0?

Altering the contents of a CVTSP, or passing in a pointer that does not point to a CVTSP created by the F_GETCVT command, may result in data integrity issues. fcntl () returns 0 if the file position and conversion data was restored. When the F_SETCVT is complete, reading and writing to the file may occur from the file offset provided by the CVTSP.


2 Answers

For Python 3.5+, Glenn Maynard's solution no longer works because of PEP-475. This is a modified version:

import signal, errno
from contextlib import contextmanager
import fcntl

@contextmanager
def timeout(seconds):
    def timeout_handler(signum, frame):
        # Now that flock retries automatically when interrupted, we need
        # an exception to stop it
        # This exception will propagate on the main thread, make sure you're calling flock there
        raise InterruptedError

    original_handler = signal.signal(signal.SIGALRM, timeout_handler)

    try:
        signal.alarm(seconds)
        yield
    finally:
        signal.alarm(0)
        signal.signal(signal.SIGALRM, original_handler)

with timeout(1):
    f = open("test.lck", "w")
    try:
        fcntl.flock(f.fileno(), fcntl.LOCK_EX)
    except InterruptedError:
        # Catch the exception raised by the handler
        # If we weren't raising an exception, flock would automatically retry on signals
        print("Lock timed out")
like image 71
remram Avatar answered Oct 28 '22 18:10

remram


As a complement to @Richard Maw answer above https://stackoverflow.com/a/32580233/17454091 (Don't have enough reputation to post a comment).

In Python 3.2 and newer, for fds to be available in sub-processes one must also provide pass_fds argument.

Complete solution ends up as:

import subprocess
def flock_with_timeout(fd, timeout, shared=True):
    rc = subprocess.call(['flock',
                          '--shared' if shared else '--exclusive',
                          '--timeout', str(timeout),
                          str(fd)],
                         pass_fds=[fd])
    if rc != 0:
        raise Exception('Failed to take lock')
like image 28
HTE Avatar answered Oct 28 '22 19:10

HTE