python fcntl

Python fcntl no se bloquea como se esperaba



(7)

En un sistema operativo basado en Debian (Ubuntu, Debian Squeeze), estoy usando Python (2.7, 3.2) fcntl para bloquear un archivo. Como entiendo por lo que leí, fnctl.flock bloquea un archivo de una manera, que se lanzará una excepción si otro cliente quiere bloquear el mismo archivo.

Construí un pequeño ejemplo, el cual esperaría lanzar una excepción, ya que primero bloqueé el archivo y, inmediatamente después, trato de bloquearlo nuevamente:

#!/usr/bin/env python # -*- coding: utf-8 -*- import fcntl fcntl.flock(open(''/tmp/locktest'', ''r''), fcntl.LOCK_EX) try: fcntl.flock(open(''/tmp/locktest'', ''r''), fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError: print("can''t immediately write-lock the file ($!), blocking ...") else: print("No error")

Pero el ejemplo simplemente imprime "Sin error".

Si divido este código en dos clientes que se ejecutan al mismo tiempo (uno bloquea y luego espera, el otro intenta bloquear después de que el primer bloqueo ya está activo), obtengo el mismo comportamiento, sin ningún efecto.

¿Cuál es la explicación de este comportamiento?

EDITAR :

Cambios según lo solicitado por nightcracker, esta versión también imprime "Sin error", aunque no esperaría que:

#!/usr/bin/env python # -*- coding: utf-8 -*- import fcntl import time fcntl.flock(open(''/tmp/locktest'', ''w''), fcntl.LOCK_EX | fcntl.LOCK_NB) try: fcntl.flock(open(''/tmp/locktest'', ''w''), fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError: print("can''t immediately write-lock the file ($!), blocking ...") else: print("No error")


Debe pasar el descriptor de archivo (que se puede obtener llamando al método fileno () del objeto de archivo). El siguiente código lanza un error IOError cuando el mismo código se ejecuta en un intérprete separado.

>>> import fcntl >>> thefile = open(''/tmp/testfile'') >>> fd = thefile.fileno() >>> fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)


Hay dos capturas. Según la documentation :

  1. Cuando la operación es LOCK_SH o LOCK_EX , también puede ser LOCK_NB en LOCK_NB bit con LOCK_NB para evitar el bloqueo en la adquisición del bloqueo. Si se utiliza LOCK_NB y no se puede adquirir el bloqueo, se generará un IOError y la excepción tendrá un atributo errno establecido en EACCES o EAGAIN (según el sistema operativo; para la portabilidad, verifique ambos valores).

    Olvidaste establecer LOCK_NB .

  2. En al menos algunos sistemas, LOCK_EX solo se puede usar si el descriptor de archivo se refiere a un archivo abierto para escritura.

    Tiene un archivo abierto para leer, que podría no ser compatible con LOCK_EX en su sistema.


He resuelto el mismo problema ... Lo resolví manteniendo el archivo abierto en una variable separada:

No funcionara

fcntl.lockf(open(''/tmp/locktest'', ''w''), fcntl.LOCK_EX | fcntl.LOCK_NB)

Trabajos:

lockfile = open(''/tmp/locktest'', ''w'') fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)

Creo que el primero no funciona porque el archivo abierto se recolecta , se cierra y se libera el bloqueo .


Lo tengo. El error en mi script es que creo un nuevo descriptor de archivo en cada llamada:

fcntl.flock(open(''/tmp/locktest'', ''r''), fcntl.LOCK_EX | fcntl.LOCK_NB) (...) fcntl.flock(open(''/tmp/locktest'', ''r''), fcntl.LOCK_EX | fcntl.LOCK_NB)

En su lugar, tengo que asignar el objeto de archivo a una variable y luego tratar de bloquear:

f = open(''/tmp/locktest'', ''r'') fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) (...) fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)

De lo que también obtengo la excepción que quería ver: IOError: [Errno 11] Resource temporarily unavailable . Ahora tengo que pensar en qué casos tiene sentido utilizar fcntl.


Publicación anterior, pero si alguien más lo encuentra, obtengo este comportamiento:

>>> fcntl.flock(open(''test.flock'', ''w''), fcntl.LOCK_EX) >>> fcntl.flock(open(''test.flock'', ''w''), fcntl.LOCK_EX | fcntl.LOCK_NB) # That didn''t throw an exception >>> f = open(''test.flock'', ''w'') >>> fcntl.flock(f, fcntl.LOCK_EX) >>> fcntl.flock(open(''test.flock'', ''w''), fcntl.LOCK_EX | fcntl.LOCK_NB) Traceback (most recent call last): File "<stdin>", line 1, in <module> IOError: [Errno 35] Resource temporarily unavailable >>> f.close() >>> fcntl.flock(open(''test.flock'', ''w''), fcntl.LOCK_EX | fcntl.LOCK_NB) # No exception

Parece que en el primer caso, el archivo se cierra después de la primera línea, presumiblemente porque el objeto de archivo es inaccesible. Al cerrar el archivo se libera el bloqueo.


Tratar:

global f f = open(''/tmp/locktest'', ''r'')

Cuando el archivo está cerrado, el bloqueo desaparecerá.


puede consultar esta post para obtener más información sobre los diferentes esquemas de bloqueo.
En cuanto a su segunda pregunta, use fcntl para bloquear a través de diferentes procesos (use lockf en lockf lugar por simplicidad). En linux lockf es solo un contenedor para fcntl , ambos están asociados con el par (pid, inode) .
1. use fcntl.fcntl para proporcionar bloqueo de archivos en todos los procesos.

import os import sys import time import fcntl import struct fd = open(''/etc/mtab'', ''r'') ppid = os.getpid() print(''parent pid: %d'' % ppid) lockdata = struct.pack(''hhllh'', fcntl.F_RDLCK, 0, 0, 0, ppid) res = fcntl.fcntl(fd.fileno(), fcntl.F_SETLK, lockdata) print(''put read lock in parent process: %s'' % str(struct.unpack(''hhllh'', res))) if os.fork(): os.wait() lockdata = struct.pack(''hhllh'', fcntl.F_UNLCK, 0, 0, 0, ppid) res = fcntl.fcntl(fd.fileno(), fcntl.F_SETLK, lockdata) print(''release lock: %s'' % str(struct.unpack(''hhllh'', res))) else: cpid = os.getpid() print(''child pid: %d'' % cpid) lockdata = struct.pack(''hhllh'', fcntl.F_WRLCK, 0, 0, 0, cpid) try: fcntl.fcntl(fd.fileno(), fcntl.F_SETLK, lockdata) except OSError: res = fcntl.fcntl(fd.fileno(), fcntl.F_GETLK, lockdata) print(''fail to get lock: %s'' % str(struct.unpack(''hhllh'', res))) else: print(''succeeded in getting lock'')

2. Utilice fcntl.lockf .

import os import time import fcntl fd = open(''/etc/mtab'', ''w'') fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) if os.fork(): os.wait() fcntl.lockf(fd, fcntl.LOCK_UN) else: try: fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError as e: print(''failed to get lock'') else: print(''succeeded in getting lock'')