2011-03-10 23 views
11

estoy usando Python 2.7fcntl.flock - cómo implementar un tiempo de espera?

Quiero crear una función de envoltura alrededor de fcntl.flock() que el tiempo de espera después de un intervalo de tiempo establecido:

wrapper_function(timeout): 

He intentado llamar en otro hilo y el uso de hilo .join (tiempo de espera), pero parece que fcntl.flock() sigue bloqueando:

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 

he mirado en soluciones para la terminación de hilos, la solución más popular parece ser sub-clasificar a threading.Thread y añadiendo una característica para aumentar una excepción en el hilo. Sin embargo, me encontré con un link que dice que este método no funcionará con llamadas nativas, y estoy seguro de que fcntl.flock() llama a una función nativa. Sugerencias?

Contexto: Estoy usando un bloqueo de archivos para crear una aplicación de instancia única, pero no quiero que una segunda instancia de la aplicación se quede ahí y cuelgue hasta que termine la primera instancia.

Respuesta

21

tiempos de espera para las llamadas al sistema están hechas con señales. La mayoría de las llamadas al sistema de bloqueo regresan con EINTR cuando ocurre una señal, por lo que puede usar alarm para implementar los tiempos de espera.

Aquí hay un administrador de contexto que funciona con la mayoría de las llamadas al sistema, haciendo que IOError se levante de una llamada al sistema de bloqueo si lleva demasiado tiempo.

import signal, errno 
from contextlib import contextmanager 
import fcntl 

@contextmanager 
def timeout(seconds): 
    def timeout_handler(signum, frame): 
     pass 

    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 IOError, e: 
     if e.errno != errno.EINTR: 
      raise e 
     print "Lock timed out" 
+1

+1, esta es exactamente la forma correcta de hacerlo.Así es también como funciona la herramienta shell ['flock (1)'] (http://linux.die.net/man/1/flock) (el código fuente está disponible en [ftp://ftp.kernel.org/pub] /linux/utils/util-linux-ng/](ftp://ftp.kernel.org/pub/linux/utils/util-linux-ng/)) –

+0

de acuerdo, esta es una mejor manera. –

+2

¿Hay alguna manera de llevar esto a cabo cuando el hilo principal no invoque necesariamente el fcntl.flock? – UsAaR33

7

Estoy seguro de que hay varias formas, pero ¿qué tal si utilizas un bloqueo sin bloqueo? Después de algunos n intentos, renunciar y salir?

Para utilizar el bloqueo de no bloqueo, incluir la bandera fcntl.LOCK_NB, como en:

fcntl.flock(self.__lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) 
+0

que sería perfecto, pero ¿dónde está este bloqueo sin bloqueo del que hablas? (Soy bastante nuevo en Python) por favor elabore –

+1

desde * pydoc fcntl *: cuando la operación es LOCK_SH o LOCK_EX, también puede ser ORed bit a bit con LOCK_NB para evitar el bloqueo en la adquisición de bloqueo –

+0

no es realmente una cosa de Python, he estado usándolo por hasta 22 años con C. –

1

Soy un fan de los bombardeos a cabo a acudir aquí, ya tratar de hacer un bloqueo de bloqueo con un tiempo de espera requiere cambios en el estado global, que hace que sea más difícil de razonar acerca de su programa, sobre todo si se trata de roscar .

Se puede bifurcar fuera un subproceso y poner en marcha la alarma que el anterior, o usted podría exec http://man7.org/linux/man-pages/man1/flock.1.html

import subprocess 
def flock_with_timeout(fd, timeout, shared=True): 
    rc = subprocess.call(['flock', '--shared' if shared else '--exclusive', '--timeout', str(timeout), str(fd)]) 
    if rc != 0: 
     raise Exception('Failed to take lock') 

Si usted tiene una nueva versión bastante de rebaño puede utilizar -E para especificar un código de salida diferente para el comando de lo contrario tendría éxito, pero no pudo tomar el bloqueo después de un tiempo de espera, por lo que puede saber si el comando falló por alguna otra razón en su lugar.

+0

¿Por qué externalizar trabajo a una entidad externa, cuando existe una solución correcta y operativa para el problema? Llamar al shell externo desde el script de Python requiere un trabajo adicional del núcleo para llamar al par fork/exec, lo que le da una penalización de rendimiento en cuanto al alcance de la memoria, la CPU y el uso de E/S. Imagine que tiene una sección crítica ejecutando una docena de veces por segundo en un sistema con mucha carga. Tal vez no sea un problema para OP, pero es bueno establecer buenas prácticas. – ArturFH

+0

Shelling out tiene muchas ventajas; es menos código para mantener y el programa externo ha sido completamente depurado. Y, presumiblemente, el programa externo se ocupa de todos los casos de esquina. No reinventar la rueda ... – presto8

Cuestiones relacionadas