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 :
Cuando la operación es
LOCK_SH
oLOCK_EX
, también puede serLOCK_NB
enLOCK_NB
bit conLOCK_NB
para evitar el bloqueo en la adquisición del bloqueo. Si se utilizaLOCK_NB
y no se puede adquirir el bloqueo, se generará unIOError
y la excepción tendrá un atributoerrno
establecido enEACCES
oEAGAIN
(según el sistema operativo; para la portabilidad, verifique ambos valores).Olvidaste establecer
LOCK_NB
.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'')